Проблемы "Указатель от целого числа / целое число от указателя без приведения"
Этот вопрос предназначен для записи часто задаваемых вопросов для всех вопросов инициализации / присваивания между целочисленными значениями и указателями.
Я хочу сделать код записи, где указатель установлен на конкретный адрес памяти, например 0x12345678
, Но при компиляции этого кода с помощью компилятора gcc я получаю "предупреждение / ошибки инициализации делает указатель из целого числа без приведения":
int* p = 0x12345678;
Точно так же этот код дает "инициализация делает целое число из указателя без приведения":
int* p = ...;
int i = p;
Если я сделаю то же самое вне строки объявления переменной, сообщение будет таким же, но оно говорит "присваивание" вместо "инициализация":
p = 0x12345678; // "assignment makes pointer from integer without a cast"
i = p; // "assignment makes integer from pointer without a cast"
Тесты с другими популярными компиляторами также дают сообщения об ошибках / предупреждениях:
- clang говорит "несовместимое преобразование целого числа в указатель"
- ICC говорит "значение типа
int
нельзя использовать для инициализации объекта типаint*
" - MSVC (кл) говорит "инициализация
int*
отличается уровнем косвенности отint
".
Вопрос: являются ли приведенные выше примеры действительными C?
И дополнительный вопрос:
Это не дает никаких предупреждений / ошибок:
int* p = 0;
Почему бы и нет?
1 ответ
Нет, это недопустимый C и никогда не был допустимым C. Эти примеры являются так называемым нарушением ограничений стандарта.
Стандарт не позволяет инициализировать / назначить указатель на целое число или целое число на указатель. Вам нужно вручную принудительно преобразовать тип с помощью приведения:
int* p = (int*) 0x1234;
int i = (int)p;
Если вы не используете приведение, код не является допустимым C, и вашему компилятору не разрешается пропускать код без отображения сообщения. В частности, это регулируется правилами простого присвоения, C17 6.5.16.1 §1:
6.5.16.1 Простое назначение
Ограничения
Одно из следующего должно иметь место:
- левый операнд имеет атомарный, квалифицированный или неквалифицированный арифметический тип, а правый имеет арифметический тип;
- левый операнд имеет атомарную, квалифицированную или неквалифицированную версию структуры или типа объединения, совместимого с типом правого;
- левый операнд имеет атомарный, квалифицированный или неквалифицированный тип указателя, и (учитывая тип, который левый операнд будет иметь после преобразования в lvalue) оба операнда являются указателями на квалифицированные или неквалифицированные версии совместимых типов, а тип, на который указывает левый, имеет все классификаторы типа, на который указывает право;
- левый операнд имеет атомарный, квалифицированный или неквалифицированный тип указателя, и (учитывая тип, который будет иметь левый операнд после преобразования в lvalue), один операнд является указателем на тип объекта, а другой - указателем на квалифицированную или неквалифицированную версию void, а тип, на который указывает слева, имеет все квалификаторы типа, на который указывает справа;
- левый операнд является атомарным, квалифицированным или неквалифицированным указателем, а правый - константой нулевого указателя; или же
- левый операнд имеет тип атомарный, квалифицированный или неквалифицированный _Bool, а правый - указатель.
В случае int* p = 0x12345678;
левый операнд - указатель, а правый - арифметический тип.
В случае int i = p;
левый операнд является арифметическим типом, а правый - указателем.
Ни один из них не соответствует ни одному из ограничений, указанных выше.
Почему int* p = 0;
работает, это особый случай. Левый операнд является указателем, а правый - константой нулевого указателя. Дополнительная информация о разнице между нулевыми указателями, константами нулевых указателей и макросом NULL.
Некоторые вещи на заметку:
Если вы назначаете необработанный адрес указателю, указатель, вероятно, должен быть
volatile
квалифицированный, учитывая, что он указывает на что-то вроде аппаратного регистра или места в памяти EEPROM/Flash, что может изменить его содержимое во время выполнения.Преобразование указателя в целое число никоим образом не гарантирует работу даже с приведением. Стандарт (C17 6.3.2.3 §5 и §6 гласит):
Целое число может быть преобразовано в любой тип указателя. За исключением случаев, указанных ранее, результат определяется реализацией, может быть неправильно выровнен, может не указывать на объект ссылочного типа и может быть представлением прерывания. 68)
Любой тип указателя может быть преобразован в целочисленный тип. За исключением указанного ранее, результат определяется реализацией. Если результат не может быть представлен в целочисленном типе, поведение не определено. Результат не обязательно должен находиться в диапазоне значений любого целочисленного типа.
Информативная нога:
68) Функции отображения для преобразования указателя в целое число или целое число в указатель предназначены для согласования со структурой адресации среды выполнения.
Кроме того, адрес указателя может быть больше, чем тот, который помещается внутри
int
, как и в случае большинства 64-битных систем. Поэтому лучше использоватьuintptr_t
от<stdint.h>