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)
Ответ на: комментарий от MOPKOBKA

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

Че?

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

Если ты пишешь на Си без понимания системы под которую ты пишешь, и считаешь что Си дофига высокоуровневый - тебе надо на питоне писать, вместо этого, а до Си не допускать вообще. Потому что он уместен только как раз в тех местах, где нужен низкоуровневый системно-специфичный дроч - ОС, драйвера, рантаймы ЯП, работа с микроконтроллерами, и все в таком духе. И во всех этих случаях тебе стандарт сам по себе во-первых, особо нахер не уперся, а во-вторых, руководствуясь только им - ты никуда не уедешь.

Касательно кода, который тут высрат.

Ты сам то понимаешь что тут происходит? Опять же, как и в примере выше - это вообще не относится к тому, что указатель это число.

У тебя компилятор первую проверку тупо выкидывает нахрен. А знаешь почему?

А потому что попробуй это скомпилировать с -O0

То что происходит у тебя в коде, на x86-64, это конкретные приколы оптимизатора GCC.

С -O0 у тебя не выведется ни A и B, и видимо, рассчитывая на это, компилятор тебе и удаляет первую проверку. Зато, если с -O0 сделать *pb = &b - 1;, вместо «плюс один» - выведет обе буквы. Более того, с минусом - будет работать и в майрософтовском VC++. За Clang не знаю.

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

P.S. Если немного модифицировать твой говнокод, вот таким образом, то выведется тебе обе буквы даже с -O2:

static int a, b;

void foo(int* pa, int* pb) {  
  if (pa == pb) {
    printf("A\n");
  } 
  if ((long)pa == (long)pb) {
    printf("B\n");
  } 
}

int main() {
  int *pa = &a, *pb = &b + 1;
  foo(pa, pb);
  return 0;
}
lovesan ★★★
()
Последнее исправление: lovesan (всего исправлений: 1)
Ответ на: комментарий от cumvillain

Если это просто номер в массиве памяти, ничего не должно мешать мне записать в N+1 ячейку, правда?

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

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

Так он переносимый в том смысле, что не надо инструкции ассемблера для каждой архитектуры зубрить, это раз. А два - всегда можно все засрать ifdef-ами, как вобщем-то в реальности и делается.

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

То что происходит у тебя в коде, на x86-64, это конкретные приколы оптимизатора GCC.

Ты тупой? Это оптимизация по стандарту которые может выполнить любой компилятор. Если сейчас MSVC его не выполняет, это не значит что у них не будет времени что бы заняться оптимизациями в будущем.

...А попробуй это скомпилировать с -O0...
...А если немного модифицировать...

Может просто выучить язык? Уже достаточно софта который не умеет собираться в -O2

Начнем с того, что тебе вообще никто не гарантирует, в каком порядке у тебя переменные распиханы по памяти

Числа этих указателей равны, ты говорил что указатели это числа. Либо кайся либо не показывай свою ограниченность.

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

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

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

Прекрасно! То есть память в C уже не просто массив, а массив с возможностью столкнуться с работой компилятора?

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

Так он переносимый в том смысле, что не надо инструкции ассемблера для каждой архитектуры зубрить, это раз. А два - всегда можно все засрать ifdef-ами, как вобщем-то в реальности и делается.

Ты не понимаешь что значит переносимый :D Переносимый – значит код, написанный по стандарту, будет работать одинаково на всех платфомах. Если твой код работает на x86, но не работает на riscv потому что ты облажался с указателями, то твой код ни разу не переносимый.

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

Это оптимизация по стандарту которые может выполнить любой компилятор.

Нет, это по-моему, ты тупой. Ты каким-то хером закладываешься на то как компилятор раскидал переменные в памяти, что естественно, делать нельзя, и пытаешься этим опровергнуть то что указатель это просто число. Это, я не знаю, это не тупость даже, это шизофрения какая-то. Между этими вещами вообще связи нет. Я, причем, тебе привел пример того - когда эти переменные лежат там где надо, и их можно достать примерно таким образом как ты хочешь.

Если сейчас MSVC его не выполняет, это не значит что у них не будет времени что бы заняться оптимизациями в будущем.

Разное расположение в памяти это, возможно, даже не оптимизация, а просто оптимизатор GCC включает другой алгоритм раскраски виртуальных регистров вот и все(обход дерева с другого конца края итд), поэтому переменные местами меняются. MSVC же складывает их в том порядке в котором были написаны.

Что, еще раз, не дает тебе права закладываться на их взаимное расположение вообще от слова совсем. И GCC очевидно считает также(впрочем выкидывание условия все-равно тупняк, но GCC таким хорошо известен). Что еще раз, никак не опровергает тезис о том что указатель это число.

Может просто выучить язык? Уже достаточно софта который не умеет собираться в -O2

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

Ну и да, как ты контрпример то, с функцией foo, объяснишь?

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

Я все еще жду хотя бы одного примера, опровергающего тезис «указатель - это всего лишь число».

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

Че?

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

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

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

код, написанный по стандарту, будет работать одинаково на всех платфомах

Напиши мне код уровнем выше хелловорлда, который будет одинаково работать на винде и линуксе. Я уж не говорю про разные процессоры. Ты вообще, походу, реальные проекты на Си не видел.

Одинаковый код для всех платформ это про питон какой-нибудь, а не про Си.

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

Ну я кстати, начинаю догадываться, что у людей в голове.

Т.е. люди вообще оторванные от практики, которые не понимают ни как ОС и процессоры работают, которые не врубаются в операционную семантику сишечки(которую диктует ОС и древний PDP-11 по сути), зато задрачиваются на синтаксис.

Т.е. без глубинного понимания чего-либо вообще, зато задрочили какие-то там куски сраного стандарта(тут конечно встает вопрос зачем - ну потому что привыкли заучивать без понимания все подряд, в школе и универе, наверное).

Вот такие же люди ругают лиспы за скобочки, и все в таком духе. Тупые. Да, херли, 95% населения, вобщем-то - идиоты, это давно известно.

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

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

Т.е. ты можешь ходить по массиву, только если зашел не туда то программа упадет с sigsegv? То есть, другими словами, ты НЕ можешь ходить по массиву, потому что как именно данные в этом массиве лежат это абсолютный implementation defined.

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

Напиши мне код уровнем выше хелловорлда, который будет одинаково работать на винде и линуксе. Я уж не говорю про разные процессоры. Ты вообще, походу, реальные проекты на Си не видел.

Любой код, который использует стандартную библиотеку, будет одинаково работать на винде и линуксе, лол. Не говоря уже про разные процессоры.

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

Т.е. люди вообще оторванные от практики, которые не понимают ни как ОС и процессоры работают, которые не врубаются в операционную семантику сишечки(которую диктует ОС и древний PDP-11 по сути), зато задрачиваются на синтаксис.

Ну ты можешь открыть код ядра Linux, сравнить количество кода в arch/ и drivers/ и перестать быть таким тупым. Рискнешь ли ты это сделать? :D

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

Код ядра линукс? Ты че несешь? Ты видел сколько там ifdef?

Или посмотри какой-нибудь dotnet runtime - даром что на C++, т.е. казалось бы более высокоуровневая херня - а туда же. Или код рантайма жабы - то же самое.

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

Лол, такое будет как раз в меньшинстве случаев IRL.

Ты мне сейчас на серьезных щщах будешь рассказывать что чтение незамапленной памяти проходит без последствий? :)))))

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

Код ядра линукс? Ты че несешь? Ты видел сколько там ifdef?

Ага, видел. Ещё я видел что там почти нет архитектурноспецифичного кода вне arch/, как раз для того, чтобы ядро работало на всех платформах. Удивительно, правда? :D

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

Любой код, который использует стандартную библиотеку, будет одинаково работать на винде и линуксе, лол. Не говоря уже про разные процессоры.

Да щаз. Например, sizeof(long) у gcc на x86_64 дает 8, а у MS VS только 4. Код одинаковый, результат разный в зависимости от компилятора, притом стандарт не нарушен.

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

Да щаз. Например, sizeof(long) у gcc на x86_64 дает 8, а у MS VS только 4. Код одинаковый, результат разный в зависимости от компилятора, притом стандарт не нарушен.

Специально для этого придумали stdint.h для тех ситуаций, когда тебе важно сколько у тебя long.

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

Сегфолт у тебя будет - в меньшинстве случаев. В большинстве случаев - ты просто тихо засрешь какой-то кусок выделенной для твоего процесса памяти. Еще в каком-то проценте случаев, особенно в случае buffer overflow - возможен даже вызов чужеродного кода - вирусы, вот это всё.

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

Сегфолт у тебя будет - в меньшинстве случаев. В большинстве случаев - ты просто тихо засрешь какой-то кусок выделенной для твоего процесса памяти. Еще в каком-то проценте случаев, особенно в случае buffer overflow - возможен даже вызов чужеродного кода - вирусы, вот это всё.

Чувак, чтение незамапленной памяти это сегфолт :D

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

Это он еще не догадывается насколько на винде всрата сишная стандартная библиотека, и насколько там у нее неоднозначное поведение во многих случаях, ввиду чего все нормальные люди используют WinAPI напрямую.

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

Но об этом еще знать надо. Я к тому, что хотя идея Си была именно в переносимости, закладываться на то, что программа, даже написанная по стандарту, будет одинаково работать на разных системах в разных компиляторах не стоит. Могут быть нюансы, которые надо учитывать для переносимости. В свое время не случайно были руководства по переписыванию с i386 на amd64. В целом одинаково, но кое-какие вещи все же работают по-разному.

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

Но об этом еще знать надо.

Да, сишникам надо читать стандарт C чтобы не исать говнокод. Но как показывает многолетняя практика, они его не читают и пишут лютый непортируемый говнокод. У самурая нет цели, есть только segmentation fault (core dumped).

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

Я к тому, что хотя идея Си была именно в переносимости, закладываться на то, что программа, даже написанная по стандарту, будет одинаково работать на разных системах в разных компиляторах не стоит.

Ох… Вот возьмем util-linux. Его можно собрать gcc, clang, он работает на musl, на glibc и вероятно ещё на каких-то libc. Он написано по стандарту и поэтому у него проблем. Я почти уверен что если мы возьмем код @lovesan мы будем сперва угорать, а потом выясним что он не будет работать нигде кроме как на x86.

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

причем, тебе привел пример

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

Что, еще раз, не дает тебе права закладываться на их взаимное расположение вообще

Числа равны, меня остальное не интересует. Мнение разработчика gcc по этому вопросу такое: если числа указателей совпадают, то это еще не значит что они равны.

Следующий пример, объясняй:

#include <stdio.h>

int zero(float *pf, int *pi)
{ 
  *pi = 1; 
  *pf = 0.f;
  return *pi;
}

int main() {
  int x = 0;
  x = zero((float*)&x, &x);
  printf("%d\n", x);
}
А тут что происходит?

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

мы будем сперва угорать, а потом выясним что он не будет работать нигде кроме как на x86.

Он вставит побольше ifdef и его код начнет собираться под ARM64, но только для определенной версии Win, с определенной версией VS, и с определенными флагами (-O00.1 -fno-all-optimizations)

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

Я не понял, а зачем вы все тут товарищу @lovesan так толсто пытаетесь намекнуть, что указатель это не только число, но и тип данных на которые он указывает?

Товарищ @lovesan упорно прикидывается лиспо-шлангом.

Как сюда скастовать Владимира? )

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

я тебе показал что указатели это не числа

Ты упорно гонишь шизофрению. Хорошо, зайдем от противного - что тогда такое указатель? Это объект? Что там кроме числа? Тайптег? Дескриптор типа? Размерность?

причем числа указателей могут быть равны, а они нет.

Ты привел абсолютно бредовый и высосанный из пальца пример, который нисколько и ни разу не опровергает то, что указатель это число. Зато он показывает, что нельзя закладываться на адрес глобальных переменных относительно друг друга, что и без тебя знали.

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

Хорошо, зайдем от противного - что тогда такое указатель?

О, я рад что заинтересовал тебя стандартом С, приятного чтения.

Если будешь не согласен, то можешь мой второй пример разобрать! Там думаю уже сам все поймешь!

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

Я уже понял, что ты полный шланг, и теоретик, который способен только на всратые хелловорлды, которые противоречат здравому смыслу вообще. И ты не только на вопросы ответить не можешь, ты еще и читать не умеешь.

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

То есть ты утверждаешь что я не могу скопировать определение из стандарта? Предлагаю устроить спор на некую сумму.

Второй пример, разбери его, мне очень интересно как ты там будешь оправдываться!

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

К слову, для тех, кто не в курсе – сложение указателя с числом не дает другого числа, оно дает другой указатель:

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

struct data {
	uint16_t a;
	uint16_t b;
};

void
main(void)
{
	struct data array[3];

	printf("%p\n", array);
	printf("%p\n", array + 1);
	printf("%p\n", array + 2);
}
$ ./test
0x7ffc2328151c
0x7ffc23281520
0x7ffc23281524

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

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

К слову, для тех, кто не в курсе – сложение указателя с числом не дает другого числа, оно дает другой указатель

К слову - арифметика определена только в пределах одного массива.

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

Сильный пример. Работает в винде, макоси и даже под DOS? Не содержет ifdef? Собирается без autotools?

Работает на всех современных и не очень архитектурах, не требуя

#ifdef ARCH_RISCV64

в каждой функции :))))

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

Это пишет человек, который не отдупляет вообще какие-то совсем базовые вещи: Почему компилятор разрешает такой код??

Иди лабы сдавай, короче, вместо сидения на лоре.

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