Преобразование типов указателей

Решил изучить преобразования указателей. И у меня возник вопрос по поводу этого кода:

unsigned short int a = 0xABCD;
unsigned int* ref_a = (unsigned int*) &a;

если я, допустим, буду и дальше работать с указателем ref_a, то я буду оперировать и с невыделенной памятью, которая нужна для представления int? Или эти два байта компилятор как-то выделит?


Ответы (4 шт):

Автор решения: user7860670

При разыменовании указателя ref_a будет неопределенное поведение, так как тип объекта, на который тот реально указывает, не совместим с unsigned int *.

→ Ссылка
Автор решения: Bloody.cpp

Компилятор ничего не выделит. Вы либо получите исключение, либо получите переменную с мусором если попытаетесь получить значение. Если попытаетесь записать туда что-то - возможно получите исключение. Нужно понимать, что в памяти ничего не подразделяется на цифры или не цифры, символы или не символы. Всё состоит из байтов, а они из битов. Потому ничего волшебного вы не получите

→ Ссылка
Автор решения: Qwertiy

Формально - неопределённое поведение.

Реально - при чтении половина инта будет мусором. А при записи - порча некоторого куска памяти. Хотя если повезёт, то ненужного из-за выравнивания данных.

→ Ссылка
Автор решения: Harry

Мне просто негде это расписать, так что простите, что ответом. Это ответ для 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

Обратите внимание на заполнение стека в начале кода, и вызов функции проверки неизменности этих данных в конце.

Поэтому говорить о генерации исключения в этом коде неверно как с точки зрения терминологической, так и идеологической. По сути, это простая запись/чтение за границами массива, причем в стеке, что никак не вызовет срабатывания защиты памяти.

Неопределенность поведения заключается в том, что при чтении "лишние" два байта не инициализированы сознательно (а могут содержать что угодно — от просто мусора до служебной информации о кадре стека), а при записи — могут просто испортить какую-то другую переменную или ту же служебную информацию о кадре стека, что (теоретически) может привести, например, в возврату из функции в совсем другую точку кода, а не в точку вызова.

→ Ссылка