Преобразование типов указателей
Решил изучить преобразования указателей. И у меня возник вопрос по поводу этого кода:
unsigned short int a = 0xABCD;
unsigned int* ref_a = (unsigned int*) &a;
если я, допустим, буду и дальше работать с указателем ref_a, то я буду оперировать и с невыделенной памятью, которая нужна для представления int? Или эти два байта компилятор как-то выделит?
Ответы (4 шт):
При разыменовании указателя ref_a будет неопределенное поведение, так как тип объекта, на который тот реально указывает, не совместим с unsigned int *.
Компилятор ничего не выделит. Вы либо получите исключение, либо получите переменную с мусором если попытаетесь получить значение. Если попытаетесь записать туда что-то - возможно получите исключение. Нужно понимать, что в памяти ничего не подразделяется на цифры или не цифры, символы или не символы. Всё состоит из байтов, а они из битов. Потому ничего волшебного вы не получите
Формально - неопределённое поведение.
Реально - при чтении половина инта будет мусором. А при записи - порча некоторого куска памяти. Хотя если повезёт, то ненужного из-за выравнивания данных.
Мне просто негде это расписать, так что простите, что ответом. Это ответ для Bloody.cpp, а заодно и всем, кому будет интересно.
Никакое т.н. исключение при такой работе Visual C++ не генерирует, это, как говорят в народе, путать теплое и мягкое. Это при отладочном режиме в среде используется дополнительный код, который заставляет отладчик выбросить окошко с сообщением о проблеме. Данный код включается ключом /RTCs (run-time check, stack) и добавляет в вызов функции дополнительную проверку, не изменяются ли дополнительные включенные в стек данные (т.н. "канарейка"). Естественно, никто в здравом уме не будет включать такой код, как и код, проверяющий выход за границы массива, например, в релиз-версию.
Вот пример кода
void f()
{
unsigned short int a = 0xABCD;
unsigned int* ref_a = (unsigned int*) &a;
*ref_a = 0x12345678;
cout << hex << a << endl;
cout << hex << *ref_a << endl;
}
а вот цена включения проверок...
Без проверки:
?f@@YAXXZ PROC ; f
; File G:\Tmp\Test\test.cpp
; Line 6
$LN3:
sub rsp, 104 ; 00000068H
; Line 7
mov eax, 43981 ; 0000abcdH
mov WORD PTR a$[rsp], ax
; Line 8
lea rax, QWORD PTR a$[rsp]
mov QWORD PTR ref_a$[rsp], rax
; Line 9
mov rax, QWORD PTR ref_a$[rsp]
mov DWORD PTR [rax], 305419896 ; 12345678H
; Line 10
.... Вывод значений
; Line 12
add rsp, 104 ; 00000068H
ret 0
?f@@YAXXZ ENDP ; f
А вот тот же код, но с ключом /RTCs:
?f@@YAXXZ PROC ; f
; File G:\Tmp\Test\test.cpp
; Line 6
$LN3:
push rdi
sub rsp, 112 ; 00000070H
lea rdi, QWORD PTR [rsp+32]
mov ecx, 20
mov eax, -858993460 ; ccccccccH
rep stosd
; Line 7
mov eax, 43981 ; 0000abcdH
mov WORD PTR a$[rsp], ax
.... Далее все так же, как и выше
; Line 12
mov rcx, rsp
lea rdx, OFFSET FLAT:?f@@YAXXZ$rtcFrameData
call _RTC_CheckStackVars
add rsp, 112 ; 00000070H
pop rdi
ret 0
?f@@YAXXZ ENDP ; f
Обратите внимание на заполнение стека в начале кода, и вызов функции проверки неизменности этих данных в конце.
Поэтому говорить о генерации исключения в этом коде неверно как с точки зрения терминологической, так и идеологической. По сути, это простая запись/чтение за границами массива, причем в стеке, что никак не вызовет срабатывания защиты памяти.
Неопределенность поведения заключается в том, что при чтении "лишние" два байта не инициализированы сознательно (а могут содержать что угодно — от просто мусора до служебной информации о кадре стека), а при записи — могут просто испортить какую-то другую переменную или ту же служебную информацию о кадре стека, что (теоретически) может привести, например, в возврату из функции в совсем другую точку кода, а не в точку вызова.