LINUX.ORG.RU

Указатель на указатель в си

 


0

2

Почему для изменения значения переменной через доступ к указатель указателя необходимо использовать две звезды * * в примере ниже, а write хватает * звезды чтобы добраться до значения?

    char test = 'a';
    char *ptr_to_char = &test;
    char **ptr_to_ptr = &ptr_to_char;
    
    *ptr_to_ptr = 'd'; 
/* Incompatible integer to pointer conversion assigning to 'char *' from 'int'*/

    **ptr_to_ptr = 'b';
/* works */
    
    write(1, *ptr_to_ptr, 1);
/* also works */


Последнее исправление: dffrwpv (всего исправлений: 4)

*ptr вроде даёт адресс в памяти, куда ссылается указатель.
&ptr извлекает данные из указателя.
тут задача видимо неограниченная двумерная матрица, надо по другому решать.

etwrq ★★★★★
()

Потому что функция write определена как

#include <unistd.h>
ssize_t write(int fd, const void buf[.count], size_t count);

Что как-бы говорит нам, что функции пофигу, что за указатель ей подсунули, главное, что она это интерпретирует как не типизированный указатель на какой-то блок данных, размером count.

По сути char *ptr_to_char = &test; получилась строка на стеке из одного символа (не терминированная, поэтому до первого нуля). Второе - указатель на эту строку. А при вызове функции указатель снят *ptr_to_ptr, что даёт нам исходную строку, которая также является char *str[] (потому что массивы - это сахар над указателями), с count=1.

PS. Код ужасен. Сотри и забудь, как страшный сон.

SkyMaverick ★★★★★
()
Последнее исправление: SkyMaverick (всего исправлений: 1)

Потому что внутри write есть своя одна звезда (что указывается типом аргумента с одной звездой).

И одну звезду применяешь ты, а одну звезду применяет write.

KivApple ★★★★★
()
Ответ на: комментарий от rupert

Это копипаст из man 2 write. Видимо, это псевдосинтаксисом должно показать, что буфер ограничивается размером параметра count.

SkyMaverick ★★★★★
()
Последнее исправление: SkyMaverick (всего исправлений: 1)
Ответ на: комментарий от rupert

Прототип здорового человека

ssize_t write(int fd, const void *buf, size_t count);

Прототип неадекватного шизоида в терминальной стадии

ssize_t write(int fd, const void buf[.count], size_t count);

В нос пукнуть тому что эту отсебятину в man понапихал.

LINUX-ORG-RU ★★★★★
()
Ответ на: комментарий от SkyMaverick

Видимо, это псевдосинтаксисом должно показать, что буфер ограничивается размером параметра count.

Мне даже интересно можно ли предположить что может быть иначе? Взять и сломать реальный прототип функции и впихнуть псевдокод от балды и даже не упомянуть про это.

Как говорится, удачи новичкам в обучении, машем ручками! Янегодую, это бред, нельзя так делать.

LINUX-ORG-RU ★★★★★
()
Ответ на: комментарий от LINUX-ORG-RU

Вопрос у авторам man-а, какбэ. Хотя, в общем-то, хоть мало-мальски знакомому с синтаксисом С-ей, по-моему, всё очевидно.

upd. Но с посылом, в целом, согласен.

SkyMaverick ★★★★★
()
Последнее исправление: SkyMaverick (всего исправлений: 1)
Ответ на: комментарий от SkyMaverick

Этот редискосон Alex Colomar

https://git.kernel.org/pub/scm/docs/man-pages/man-pages.git/commit/man2/write.2?id=c64cd13e002561c6802c6a1a1a8a640f034fea70

Пишет что

  • Various pages: SYNOPSIS: Use VLA syntax in ‘void *’ function parameters

Чиво? Какой ещё VLA syntax? Почему информацию про это можно найти только основательно порывшись в коммитах гита (это я не тебе лично вопрос, а в целом продолжаю бомбить)

Этот чёрт придумал хероту https://gcc.gnu.org/pipermail/gcc/2022-November/239865.html

И началось. То есть он тупо взял придумал псевдокод, назвал его VLA syntax и начал его совать, притом что знают про него он и кто прочёл список рассылки всё. А завтра он решит имена функций капсом писать, ну, для наглядности, а чё.

Нет я понимаю что у него мол идея и благие намерения, но в этом его псевдокоде просто нет никакого смысла.

Я всё. Скоро все man 2 xxx будут в этим инновационным псевдокодом обмазаны. Всем добра… @rupert

LINUX-ORG-RU ★★★★★
()
Последнее исправление: LINUX-ORG-RU (всего исправлений: 1)
Ответ на: комментарий от LINUX-ORG-RU

О, я думал я один полез в коммиты. И этот чёрт зачем-то ссылается на C23 Charter, я уж решил, что это какой-то новый синтаксис в стандарте — нет, чисто фантазии (хотя он их намерен продавить в GCC, как я понял).

alegz ★★★★
()
Ответ на: комментарий от SkyMaverick

Так у void же нет размера, поэтому запись вида void buf[n] не имеет смысла. Лучше уж явно писать void* buf. Автору этого определения точно нужно гвоздь в голову забить. Блин вместо предельно понятного (…void* buf, size_t buf_size) написать семантически бессмысленное (…void buf[count], size_t count), которое к тому же создает у читателя ложное впечатление что туда нужно передавать однородный массив элементов и указывать в count количество элементов

cobold ★★★★★
()
Последнее исправление: cobold (всего исправлений: 1)
Ответ на: комментарий от cobold

Да я, как-бы, ни с кем и не спорил. Я привёл то, что ТС и так-бы в man-е увидел, если бы его открыл.

В принципе, для знакомого с синтаксисом С псевдокод понятен, что хотели отобразить (после фразы «что за нах… тут написан»). Для новичка, да, могут быть траблы. Хотя, ИМХО учить язык по системному man-у - «ну такое».

SkyMaverick ★★★★★
()
Ответ на: комментарий от lovesan

Ты не знаешь С. Это не просто число, иначе при одинаковых числах в С компилятор бы не выдавал != для двух указателей.

MOPKOBKA ★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 1)
Ответ на: комментарий от MOPKOBKA

Мой любимый пример для разрыва жопы сторонникам «указатель это всего лишь число»:

$ cat test.c
#include <stdint.h>
#include <stdio.h>
#include <string.h>

int
main(void)
{
	uint8_t a[16] = {0};
	uint8_t b[16] = {0};

	printf("a = %p\n", a);
	printf("b = %p\n", b);

	memcpy(a, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 32);

	if (memcmp(a, b, 16) == 0)
		puts("match!");

	return 0;
}
$ ./test
a = 0x7ffdfbfb23f0
b = 0x7ffdfbfb2400
*** buffer overflow detected ***: terminated
zsh: IOT instruction (core dumped)  ./test
cumvillain
()
Ответ на: комментарий от LINUX-ORG-RU

Ну VLA придумал не он, VLA ввели в C99. Другое дело что в VLA нет точки:

int foo(size_t len, const uint8_t buf[len])

Проблема в том, что

int foo(const uint8_t buf[len], size_t len)

тоже работать не будет, потому что len должен появиться в функции раньше чем buf.

cumvillain
()
Ответ на: комментарий от cumvillain

Твой пример, естественно, ничего не показывает. Потому что ты там делаешь дурь - выходишь за границы массива в переменной. Это вообще ортогональная вещь. Тебе туда GCC просто проверку напихал, статически зная размер массива, вот и всё. Защита от дурака. И при сборке, он тебе явно пишет, что там за проверка, и какая функция используется вместо банального memcpy.

lovesan@ubuntu:~$ gcc -O2 -g -o foo foo.c
In file included from /usr/include/string.h:535,
                 from foo.c:3:
In function ‘memcpy’,
    inlined from ‘main’ at foo.c:14:2:
/usr/include/x86_64-linux-gnu/bits/string_fortified.h:29:10: warning: ‘__builtin___memcpy_chk’ writing 32 bytes into a region of size 16 overflows the destination [-Wstringop-overflow=]
   29 |   return __builtin___memcpy_chk (__dest, __src, __len,
      |          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   30 |                                  __glibc_objsize0 (__dest));
      |                                  ~~~~~~~~~~~~~~~~~~~~~~~~~~
lovesan@ubuntu:~$ ./foo
a = 0x7ffdc83c8000
b = 0x7ffdc83c8010
*** buffer overflow detected ***: terminated
Aborted

Теперь попробуй эту проверку вырубить:

-D_FORTIFY_SOURCE=0

lovesan@ubuntu:~$ gcc -D_FORTIFY_SOURCE=0 -O2 -g -o foo foo.c
<command-line>: warning: "_FORTIFY_SOURCE" redefined
<built-in>: note: this is the location of the previous definition
foo.c: In function ‘main’:
foo.c:14:9: warning: ‘memcpy’ writing 32 bytes into a region of size 16 overflows the destination [-Wstringop-overflow=]
   14 |         memcpy(a, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 32);
      |         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
foo.c:8:17: note: destination object ‘a’ of size 16
    8 |         uint8_t a[16] = {0};
      |                 ^
lovesan@ubuntu:~$ ./foo
a = 0x7ffeeae3f590
b = 0x7ffeeae3f5a0
match!

И да, указатель это всего лишь адрес байта в памяти, если не брать сегментные модели памяти x86 и подобную экзотику(которые нигде не используются уже), и разнообразную дурь типа C++, где указатели на методы есть, многокомпонентные, итд.

Современные ОС - это рантаймы Си. В Си - линейная прямая модель памяти.

lovesan ★★
()
Ответ на: комментарий от MOPKOBKA

«Стандарт» который ты там зубрил, про UB и прочее - нихрена тебе не дает вообще от слова совсем. На Си пишут всегда зная конкретное ABI конкретной комбинации ОС и процессора, и как раз в самых распространенных комбинациях - все как я сказал, см. выше ремарку про сегментную адресацию итд, которые нигде уже не используются.

И вот раз ты этого не знаешь, ты видимо реально не знаешь Си, и достаточно на нем не писал, теоретик.

lovesan ★★
()
Ответ на: комментарий от lovesan

И да, указатель это всего лишь адрес байта в памяти, если не брать сегментные модели памяти x86 и подобную экзотику(которые нигде не используются уже), и разнообразную дурь типа C++, где указатели на методы есть, многокомпонентные, итд.

«Если выкинуть все граничные случаи то мой говнокод даже работает!». Сказал как настоящий сишник, чо уж.

cumvillain
()
Ответ на: комментарий от lovesan

UB и прочее - нихрена тебе не дает вообще от слова совсем

Очевидно это дает понимание что код не будет работать, как задумывал программист считающий что указатель это число. Повторю еще раз, компилятор просто не сгенерирует код который ты ожидаешь. Выбросит проверки, уберет обращения, итд.

На Си пишут всегда зная конкретное ABI конкретной комбинации ОС и процессора

На компилятор это не влияет, он следует стандарту С, внутренняя реализация это не то что я обсуждаю.

Очевидно что для x86 указатель будет представлен в итоговом коде как ячейка с числом, но перед этим компилятор будет работать исходя из абстракций С.

static int a, b;

int main() {
  int *pa = &a, *pb = &b + 1;
  if (pa == pb) {
    printf("A\n");
  } 
  if ((long)pa == (long)pb) {
    printf("B\n");
  } 
  return 0;
} 
./a.out 
B

И вот раз ты этого не знаешь

Какой глупый вывод.

MOPKOBKA ★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 5)
Ответ на: комментарий от cumvillain

Его велосипед к массивам переменной длинны отношения вообще не имеет как по смыслу так и по синтаксису. Более того массивы переменной длинны ака VLA C99 работают исключительно на стеке, а синтаксис является по сути сахаром к функции alloca. Чистое самодурство.

чтобы можно было ссылаться на длину, стоящую после буфера.

В контексте write звучит как дичь. Создать пустой массив размером N на этапе передачи в стек параметров для функции и записать из этого пустого массива с мусором из стека данные в файл.

Я вот честно просто не вижу никаких вариантов того что это может быть полезно как расширение в реальном коде и как нашлёпка в документации. Вот не вижу и всё. Может кто объяснит или просто придумает не притянутую за уши пользу.

LINUX-ORG-RU ★★★★★
()
Ответ на: комментарий от LINUX-ORG-RU

Более того массивы переменной длинны ака VLA C99 работают исключительно на стеке

Нет, можно кастануть во VLA память из malloc, и это имеет смысл так как будет нормальный многомерный массив в Fortran стиле, а не указатели на указатели

а синтаксис является по сути сахаром к функции alloca

Нет, у alloca время жизни массива вся функция, у VLA только текущая область

Создать пустой массив размером N на этапе передачи в стек параметров для функции и записать из этого пустого массива с мусором из стека данные в файл.

Не знаю что ты имел виду, но синтаксис void f(int n, char buf[n]); уже работает в gcc

MOPKOBKA ★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 5)
Ответ на: комментарий от lovesan

Твой пример, естественно, ничего не показывает. Потому что ты там делаешь дурь - выходишь за границы массива в переменной.

Комментарием выше ты говорил что указатель это номер байта в массиве памяти. Если это просто номер в массиве памяти, ничего не должно мешать мне записать в N+1 ячейку, правда? А если что-то мешает мне записать в N+1 ячейку, значит указатель наверное не совсем «просто номер байта», правда?

cumvillain
()
Ответ на: комментарий от LINUX-ORG-RU

Да, но я про то что смысла в таком порой просто нет, можно, но нет смысла.

Смысл в таком есть, потому что цомпилятор может делать выводы о размере массиве и идентичности или неидентичности размеров разных массивов. Например эта штука позволила бы написать ARRAY_SIZE() который не выдавал бы лажу на указателях.

cumvillain
()