#c #pointers #gcc #gcc-warning
#c #указатели #gcc #gcc-предупреждение
Вопрос:
Когда я компилирую этот код с помощью gcc 7.3.0, я получаю «присвоение из несовместимого типа указателя».
int intVar = 1;
char* charPointer;
charPointer = amp;intVar;
printf("%d", *charPointer);
Пока все хорошо. Я могу справиться с этим, выполнив присвоение указателя следующим образом:
charPointer = (char*)amp;intVar;
Теперь я сомневаюсь: чем отличается второй случай? Я все еще могу все испортить, если я не приведу charPointer к int *, когда я, например, увеличиваю его на n или разыменовываю его. Итак, почему компилятор действует по-разному в этих двух случаях? Почему его должно волновать, если тип указателя не совпадает во время присвоения? Я просто хотел бы понять логику, стоящую за этим.
Комментарии:
1. Второй случай ничем не отличается. Вы просто явно указываете компилятору «привести этот указатель к
char *
«, тогда как без приведения неясно, было ли это намеренно или является потенциальной ошибкой. Разница в том, что один компилируется, а другой нет.
Ответ №1:
Из-за случайного изменения типа int *
на char *
компилятор предупреждает о потенциальных подводных камнях. При приведении компилятор предполагает, что программисты знают, что они делают.
В C указатели различных типов могут находиться в разных местах, иметь разные размеры и кодироваться по-разному.
Очень часто для всех указателей на объекты ( int*
, char *
, struct foo *
) используется одинаковый размер и кодировка, но, в общем случае, это не обязательно.
Нередко указатели на функции имеют размер, отличный от размера указателей на объекты.
char *
и void *
имеют одинаковый размер и кодировку, а указатели символов указывают на данные с минимальным требованием к выравниванию. Преобразование указателя, не являющегося char*
объектом, в char *
всегда «рабочий». Обратное не всегда верно. приводит к неопределенному поведению (UB).
Преобразование в безсимвольный указатель также сопряжено с риском сглаживания (компилятору необходимо отслеживать, что изменения данных с помощью одного типа указателя отражаются в другом). Может привести к очень странному неопределенному поведению.> Улучшенный код позволяет избежать изменения типа указателя на 1 и, при необходимости, является явным с приведением.
1 Исключения включают в себя изменение указателя объекта на void *
или форму void*
, когда нет проблем с выравниванием.
Ответ №2:
Преобразования указателей в некотором роде опасны.
Если тип указателя, из которого выполняется преобразование, недостаточно выровнен по целевому типу, вы получаете UB (== неопределенное поведение; прочитайте, что это такое, если у вас еще нет) уже при преобразовании.
В противном случае, если вы обычно получаете UB при разыменовании, потому что строгие правила сглаживания языка C требуют, чтобы вы обращались к объектам через типы lvalue, достаточно совместимые с их эффективным типом.
Хотя последний абзац не совсем применим к преобразованиям в указатели char, поскольку указатели char могут иметь псевдонимы любого типа, предупреждение (компиляторы также могут сделать это серьезной ошибкой) все еще полезно, потому что преобразование все еще довольно опасно.
printf("%d", *(char*)amp;(int){0xFFFF});
вы получите только первый байт (зависит от порядкового номера, является ли он наиболее значимым или наименее значимым), выводя 255 (если char
тип реализации беззнаковый) или -1 (если он подписан).
printf("%d", *amp;(int){0xFFFF});
получит все байты, которые находятся в int
.
Если компилятор позволяет вам присваивать a char *
из int *
, просто выдав предупреждение, он должен вести себя так же, как при приведении, но приведение необходимо для соответствия вашего C (и для того, чтобы компилятор не сообщал о преобразовании).
Комментарии:
1. «вывод 255 (если тип символа реализации беззнаковый) или -1 (если он подписан).» —> приводит к разным результатам в зависимости от порядкового номера.
Ответ №3:
Как говорит @Mike Holt, на самом деле это ничем не отличается, за исключением того, что вы сказали компилятору «Не волнуйся, я намерен это сделать».
Компилятор действительно беспокоится, потому что присвоение указателю другого типа обычно не то, что вы хотите делать. Ваш код сообщает компилятору «Обрабатывайте память, содержащую эту переменную, как если бы она содержала переменную другого типа». Это почти наверняка поведение, зависящее от платформы, и, возможно, неопределенное поведение, в зависимости от типов.
Комментарии:
1. Компилятор действительно беспокоится, потому что стандарт говорит, что он должен выводить диагностические данные.