Я изучаю указатели на C. Пробовав кучу вещей, чтобы убедиться, что я понимаю, как работают указатели, я нашел кое-что, чего не понимаю. Вот фрагмент кода, о котором идет речь:
int b = 30;
printf("(void *)(size_t)b: %p\n", (void *)(size_t)b);
Выход:
(void *)(size_t)b: 0x1e
Это значение 30, хранящееся в «b», в шестнадцатеричном формате. Я поместил выражение внутрь, чтобы знать, какое из них какое в консоли.
Я компилирую код с GCC 13.2.1-6 и флагами
-Wall -Werror -Wextra -std=c17 -pedantic-errors
на машине Linux x86-64. Я не знаю, актуально ли это (я новичок), но для меня int имеет 4 байта.
Код компилируется нормально, без ошибок. Я не мог понять, почему он выдает такой результат.
Я ожидал ошибки, потому что я использую «b» вместо «&b».
Если бы вы присвоили его указателю, а не приводили его, вы бы получили как минимум предупреждение. Но если вы приведете целое число к указателю, вы обычно не получите предупреждение.





int b = 30;
Теперь у вас есть значение int 30 (0x1e).
(size_t)b
Вы указываете компилятору рассматривать это как значение size_t 30 (0x1e).
(void *)(size_t)b
Вы указываете компилятору рассматривать это значение size_t 30 (0x1e) как void *.
printf("%p", (void *)(size_t)b);
Вы печатаете значение указателя, равное 30 (0x1e). Обратите внимание, что %p печатает указатель, а не то, на что он указывает.
И вы не должны разыменовывать этот указатель (путем добавления звездочки перед (void *)(size_t)b), поскольку ваш указатель недействителен, т. е. на самом деле он не указывает на что-то, поэтому его разыменование будет неопределенным поведением (т. е. плохим).
Если вы хотите напечатать адрес b, вам нужно взять этот адрес, используя адресный оператор &:
int b = 30;
printf("%p", (void *)&b);
Выражение (void *)(size_t)b уже является неопределённым поведением в Стандарте C, если только по этому адресу не существует допустимый объект. Конкретные платформы могут определять поведение
@M.M Насколько я понимаю, приведение целого числа к void * не является неопределенным поведением. (Если бы это было так, у нас были бы проблемы с #define NULL (void *)0.) Если я ошибаюсь, мне было бы интересно, из какой части стандарта вы взяли это утверждение.
UB оценивает результат выражения. Значение указателя при оценке должно указывать на действительный объект или на ноль (примечание: я намеренно говорю «оцененный», а не «разыменованный»)
Для обоснования представьте себе систему, которая автоматически вызывает аппаратную ловушку, если регистр указателя загружается с адресом вне допустимого адресного пространства.
@M.M Мне все равно хотелось бы узнать об этом главу и стих, если бы вы могли их дать, потому что я знаю несколько систем, которые их используют (например, во время ранней загрузки).
C11 6.3.2.3/5 — результатом этого преобразования может быть представление ловушки (которое определено в 6.2.6.1/5).
@M.M Преобразование целого числа в указатель - это не UB. Вы получаете UB только в том случае, если результатом преобразования является представление ловушки, а затем считывается значение lvalue, в котором хранится результат.
@dbush Результат считывается (оценивается выражение), передавая его в printf. Кроме того, результат приведения никогда не является lvalue.
@M.M Приведение не является lvalue, а параметром, данным функции (по крайней мере, если это не параметр с переменным числом аргументов, а если это так, я не уверен).
@M.M Ах, теперь я понял. Я имел в виду действительные физические адреса для MMIO, за исключением произвольных, как здесь. Спасибо за разъяснения.
@DevSolar Не могли бы вы отредактировать свой ответ с учетом этого обсуждения? Я думаю, это ответ на мой вопрос. Всем спасибо.
@newbie: Ну... на самом деле ничто в этом обсуждении не отвечает ни на что на ваш вопрос. Было бы полезно, если бы вы объяснили, чего вы ожидали. Вы погружаетесь в неопределенное поведение, но результаты, которые вы получаете, легко объяснимы.
@newbie Подожди... ты ожидал предупреждения для актеров? В этом случае знайте, что приведения в стиле C ((typename)) — это то, что в C++ называется reinterpret_cast. Нет никакой проверки работоспособности ни по типу, ни по размеру, ни по содержимому. Обращаться осторожно. ;-)
@DevSolar Да, я ожидал предупреждения, которое превратилось в ошибку из-за флага -Werror. Благодаря всем комментариям здесь и в моем вопросе мне удалось немного лучше понять ситуацию, но не до конца. У меня были неправильные представления о приведении типов и назначении указателей, хотя, честно говоря, эта часть о UB от меня ускользнула.
@newbie На самом деле это следствие. Очевидно, что «жесткое приведение» некоторого «случайного» целочисленного значения, которое не должно быть указателем, в указатель - это катастрофа, ожидающая своего часа. Мы с М.М обсуждали, в какой момент именно было выполнено действие — при разыменовании («использовании») указателя (чего ваша программа не делает) или уже при его приведении и передаче в printf. Если вас интересует мелкий шрифт, задайте новый вопрос (после проверки того, что на него уже нет ответа).
Нет никакой гарантии, что так и будет. Как говорится, это наиболее вероятный результат.
bимеет значение30, которое в шестнадцатеричном формате равно0x1e. Приведение кsize_tэтого не меняет. Приведение кvoid *просто создает указатель на это значение, которое на вашей платформе и значение30сохраняет это значение как указатель. Итак, у вас получился указательvoid *со значением30или0x1e. Чего вы ожидали от этого?