Оценка выражения в printf с помощью %p

Я изучаю указатели на 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 имеет значение 30, которое в шестнадцатеричном формате равно 0x1e. Приведение к size_t этого не меняет. Приведение к void * просто создает указатель на это значение, которое на вашей платформе и значение 30 сохраняет это значение как указатель. Итак, у вас получился указатель void * со значением 30 или 0x1e. Чего вы ожидали от этого?

Tom Karzes 09.05.2024 02:38

Я ожидал ошибки, потому что я использую «b» вместо «&b».

newbie 09.05.2024 02:42

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

Tom Karzes 09.05.2024 02:42
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
3
81
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий
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 09.05.2024 02:39

@M.M Насколько я понимаю, приведение целого числа к void * не является неопределенным поведением. (Если бы это было так, у нас были бы проблемы с #define NULL (void *)0.) Если я ошибаюсь, мне было бы интересно, из какой части стандарта вы взяли это утверждение.

DevSolar 09.05.2024 02:45

UB оценивает результат выражения. Значение указателя при оценке должно указывать на действительный объект или на ноль (примечание: я намеренно говорю «оцененный», а не «разыменованный»)

M.M 09.05.2024 02:50

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

M.M 09.05.2024 02:51

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

DevSolar 09.05.2024 02:57

C11 6.3.2.3/5 — результатом этого преобразования может быть представление ловушки (которое определено в 6.2.6.1/5).

M.M 09.05.2024 03:04

@M.M Преобразование целого числа в указатель - это не UB. Вы получаете UB только в том случае, если результатом преобразования является представление ловушки, а затем считывается значение lvalue, в котором хранится результат.

dbush 09.05.2024 03:30

@dbush Результат считывается (оценивается выражение), передавая его в printf. Кроме того, результат приведения никогда не является lvalue.

M.M 09.05.2024 05:14

@M.M Приведение не является lvalue, а параметром, данным функции (по крайней мере, если это не параметр с переменным числом аргументов, а если это так, я не уверен).

dbush 09.05.2024 05:22

@M.M Ах, теперь я понял. Я имел в виду действительные физические адреса для MMIO, за исключением произвольных, как здесь. Спасибо за разъяснения.

DevSolar 09.05.2024 14:00

@DevSolar Не могли бы вы отредактировать свой ответ с учетом этого обсуждения? Я думаю, это ответ на мой вопрос. Всем спасибо.

newbie 09.05.2024 19:45

@newbie: Ну... на самом деле ничто в этом обсуждении не отвечает ни на что на ваш вопрос. Было бы полезно, если бы вы объяснили, чего вы ожидали. Вы погружаетесь в неопределенное поведение, но результаты, которые вы получаете, легко объяснимы.

DevSolar 09.05.2024 20:05

@newbie Подожди... ты ожидал предупреждения для актеров? В этом случае знайте, что приведения в стиле C ((typename)) — это то, что в C++ называется reinterpret_cast. Нет никакой проверки работоспособности ни по типу, ни по размеру, ни по содержимому. Обращаться осторожно. ;-)

DevSolar 09.05.2024 20:50

@DevSolar Да, я ожидал предупреждения, которое превратилось в ошибку из-за флага -Werror. Благодаря всем комментариям здесь и в моем вопросе мне удалось немного лучше понять ситуацию, но не до конца. У меня были неправильные представления о приведении типов и назначении указателей, хотя, честно говоря, эта часть о UB от меня ускользнула.

newbie 09.05.2024 23:23

@newbie На самом деле это следствие. Очевидно, что «жесткое приведение» некоторого «случайного» целочисленного значения, которое не должно быть указателем, в указатель - это катастрофа, ожидающая своего часа. Мы с М.М обсуждали, в какой момент именно было выполнено действие — при разыменовании («использовании») указателя (чего ваша программа не делает) или уже при его приведении и передаче в printf. Если вас интересует мелкий шрифт, задайте новый вопрос (после проверки того, что на него уже нет ответа).

DevSolar 09.05.2024 23:37

Другие вопросы по теме