Страницы

Поиск по вопросам

пятница, 26 октября 2018 г.

scanf %p и касты указателей

http://codepad.org/WD1oWXL8
#include
int main(void) { int *p; scanf("%p", &p); return 0; }
Line 6: warning: format '%p' expects type 'void**', but argument 2 has type 'int**'
Исправляем на явный каст:
http://codepad.org/TR1XEBKP
scanf("%p", (void**)&p);
Line 6: warning: dereferencing type-punned pointer will break strict-aliasing rules
Пробуем void *
http://codepad.org/pH4lUnF5
scanf("%p", (void*)&p);
Line 6: warning: format '%p' expects type 'void**', but argument 2 has type 'void*'
В итоге скармливаем двойной каст:
http://codepad.org/1UlOShQJ
scanf("%p", (void**)(void*)&p);
Наконец-то компилируется.
Вопрос:
Зачем именно такая диагностика с указателями была добавлена и какие потенциальные проблемы может вызвать чтение указателя, отличного от void*?


Ответ

Ваш вопрос помечен тагом С++, но все таки для начала пару слов о С:
В языке С указатель типа int * формально может иметь объектное представление, отличное от объектного представления указателя типа void *. В языке С одинаковыми объектными представлениями обладают только все указатели на struct типы (между собой), все указатели на union типы (между собой), а также указатели void * и [signed/unsigned] char * (между собой) (плюс, понятное дело, их cv-квалифицированные вариации). Больше никаких совпадений объектных представлений указателей не гарантируется.
Это означает, что попытка записать значение типа void * в указатель типа int * через переинтерпретацию памяти (type punning) не является формально корректной с точки зрения языка. Язык однозначно говорит, что доступ к объекту типа int * как к lvalue типa void * любым способом, кроме как через union, ведет к неопределенному поведению (см. 6.5/7). Это так называемое правило (или аксиома) strict aliasing.
Поэтому не существует формально портабельного способа сделать scanf("%p" напрямую в указатель типа int *. Даже если объектные представления этих типов совпадают, и даже если ваши попытки "задушить" предупреждения компилятора увенчались успехом, это все равно не означает, что ваша программа будет работать корректно. Компилятор, опираясь на вышеупомянутые правила strict alising, имеет полное право полагать, что ваш scanf("%p" не модифицирует указатель p (ибо он никак не может легально его модифицировать), т.е. игнорировать информационную зависимость между вызовом scanf и значением p.
Юные "пионэры-практики" из секты "С - это такой портабельный ассемблер" довольно долго умудрялись игнорировать соображения strict aliasing и применяли type punning в своем коде направо и налево, считая это чем-то вроде секретного знания, отличающего их от "теоретиков". Это продолжалось до тех пор пока GCC и другие компиляторы не начали активно использовать в своих оптимизациях ценнейшую информацию, основанную на аксиомах strict aliasing. Вой, поднявшийся в результате из болот "практиков" такого пошиба, окончательно не утих и по сей день. Вот именно от вляпывания в это болото и пытается предохранить вас компилятор своими предупреждениями.
Если вы хотите прочитать значение для вашего p через scanf, то пожалуй единственный цивилизованный путь - это
int *p; void *v; scanf("%p", &v); p = v; // с явным кастом для C++
Или, если уж вам неймется устроить type punning, то - через union
union { int *p; void *v } u; scanf("%p", &u.v); p = u.p;
Type punning через union был таки легализован в C99 (TC3, кажется). Разумеется, с оговоркой, что вся ответственность за корректность получаемого объектного представления ложится на вас.
Вышесказанное имеет прямое отношение и к С++, за исключением того, что type punning через union в языке С++ не легализован.

Комментариев нет:

Отправить комментарий