2015-05-16 1 views
3

Компиляция с -O2 (или -O3, если на то пошло) и запуск этой программы дает интересные результаты на моей машине.C++ pointer weird undefined behavior

#include <iostream> 

using namespace std; 

int main() 
{ 
    // Pointer to an int in the heap with a value of 5 
    int *p = new int(5); 
    // Deallocate the memory, but keep a dangling pointer 
    delete p; 
    // Write 123 to deallocated space 
    *p = 123; 
    // Allocate a long int in the heap 
    long *x = new long(456); 

    // Print values and pointers 
    cout << "*p: " << *p << endl; 
    cout << "*x: " << *x << endl; 
    cout << "p: " << p << endl; 
    cout << "x: " << x << endl; 

    cout << endl << "Changing nothing" << endl << endl; 

    // Print again without changing anything 
    cout << "*p: " << *p << endl; 
    cout << "*x: " << *x << endl; 
    cout << "p: " << p << endl; 
    cout << "x: " << x << endl; 

    return 0; 
} 

g ++ -O2 code.cc; ./a.out

*p: 123 
*x: 456 
p: 0x112f010 
x: 0x112f010 

Changing nothing 

*p: 456 
*x: 456 
p: 0x112f010 
x: 0x112f010 

То, что я делаю, пишу в куче перераспределена int указывает p, а затем выделение долго с адресом x. Мой компилятор последовательно помещает длинный на тот же адрес, что и p ->x == p. Теперь, когда я разыскиваю p и печатаю его, он сохраняет значение 123, хотя он был переписан с длинным 456. *x затем печатается как 456. Что еще более странно, так это то, что позже, ничего не меняя, печатайте то же значение дает ожидаемые результаты. Я думал, что это метод оптимизации, который инициализирует * x, когда он используется после печати значения *p, что объясняет это. Однако objdump говорит что-то другое. Вот усеченный и прокомментированы objdump -d a.out:

00000000004008a0 <main>: 
    4008a0: 41 54     push %r12 
    4008a2: 55      push %rbp 

Most likely the int allocation, where 0x4 is the size (4 bytes) 
    4008a3: bf 04 00 00 00   mov $0x4,%edi 
    4008a8: 53      push %rbx 
    4008a9: e8 e2 ff ff ff   callq 400890 <[email protected]> 

I have no idea what is going on here, but the pointer p is in 2 registers. Let's call the other one q. 
q = p; 
    4008ae: 48 89 c3    mov %rax,%rbx 

    4008b1: 48 89 c7    mov %rax,%rdi 

*p = 5; 
    4008b4: c7 00 05 00 00 00  movl $0x5,(%rax) 

delete p; 
    4008ba: e8 51 ff ff ff   callq 400810 <[email protected]> 

*q = 123; 
    4008bf: c7 03 7b 00 00 00  movl $0x7b,(%rbx) 

The long allocation and some other stuff (?). (8 bytes) 
    4008c5: bf 08 00 00 00   mov $0x8,%edi 
    4008ca: e8 c1 ff ff ff   callq 400890 <[email protected]> 
    4008cf: 44 8b 23    mov (%rbx),%r12d 
    4008d2: be e4 0b 40 00   mov $0x400be4,%esi 
    4008d7: bf c0 12 60 00   mov $0x6012c0,%edi 

Initialization of the long before the printing 
*p = 456; 
    4008dc: 48 c7 00 c8 01 00 00 movq $0x1c8,(%rax) 

    4008e3: 48 89 c5    mov %rax,%rbp 

The printing 
    4008e6: e8 85 ff ff ff   callq 400870 <[email protected]> 
........ 

сейчас, хотя *p была перезаписана long инициализации (4008dc), он по-прежнему печатается 123.

Я надеюсь, что я сделал какой-то смысл здесь, и Спасибо за любую помощь.

, чтобы сделать себя ясным: Я пытаюсь выяснить, что происходит за кулисами, что делает компилятор, и почему полученный скомпилированный код не соответствует выходу. Я ЗНАЮ, ЧТО ЭТО НЕ УКАЗАН ПОВЕДЕНИЕ И ЧТО НИЧЕГО МОЖЕТ ПРОИЗОЙТИ. Но это означает, что компилятор может создавать любой код, а не то, что CPU будет составлять инструкции. Любые идеи приветствуются.

PS: Не беспокойтесь, я не собираюсь использовать это где-нибудь;)

EDIT: На моего друга машины (OS X), что дает ожидаемые результаты даже при оптимизации.

+7

Вы пытаетесь осмыслить неопределенное поведение. Наслаждайтесь процессом. –

+0

Есть ли вопрос, который я пропустил? Также ... »[...] * дает ожидаемые результаты *« hehe good one =). – luk32

+0

@ luk32 О, правильно: D Я не задал вопрос. Я просто пытаюсь понять, что происходит за кулисами. Пока это не имеет никакого смысла. : D – sammko

ответ

4

Вы слишком быстро перестали смотреть на свой вывод (или, по крайней мере, вы не опубликовали следующие несколько строк, которые имеют отношение к вашему вопросу). Они, вероятно, выглядеть примерно так:

movl %r12d, %esi 
movq %rax, %rdi 
call _ZNSolsEi 
movq %rax, %rdi 
call _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_ 

rbx и r12 регистры, которые должны быть сохранены через вызовы функций в x64 ABI используется GCC на Linux. После выделения long, вы видите эту инструкцию:

mov (%rbx),%r12d 

использований rbx ранее в потоке команд включают в себя:

mov %rax,%rbx  ; store the `p` pointer in `rbx` 

... 

movl $0x7b,(%rbx) ; store 123 where `p` pointed (even though it has been freed before) 

... 

mov (%rbx),%r12d ; read that value - 123 - back and into `r12` 

затем вы видите в этом фрагменте я написал выше, что является разбирайте, не превратить его в свой вопрос и соответствует части cout << "*p: " << *p << endl заявления:

movl %r12d, %esi ; put 123 into `esi`, which is used to pass an argument to a function call 

И 123 печатается.

+0

О, вау, да, это именно то, что вызвало это. Большое спасибо за полезное объяснение. – sammko

1

Как вы упомянули, это может быть связано с оптимизацией, выполняемой компилятором. Если вы скомпилируете с -O0, тогда это будет печать 456 для значений. Поскольку p был удален и x был немедленно выделен, x будет указывать на тот же адрес, на который указывал p (может быть, это не тот же случай всегда, но в ваших тестах это наиболее вероятно). Следовательно, * p и * x должны иметь де-ссылку на одно и то же значение. Если вы измените порядок операций печати, тогда всегда 456 будет напечатано для значений. Я изменил порядок первых двух COUT операторов в коде, как показано ниже:

#include <iostream> 

using namespace std; 

int main() 
{ 
    // Pointer to an int in the heap with a value of 5 
    int *p = new int(5); 
    // Deallocate the memory, but keep a dangling pointer 
    delete p; 
    // Write 123 to deallocated space 
    *p = 123; 
    // Allocate a long int in the heap 
    long *x = new long(456); 

    // Print values and pointers 
    cout << "*x: " << *x << endl; 
    cout << "*p: " << *p << endl; 
    cout << "p: " << p << endl; 
    cout << "x: " << x << endl; 

    cout << endl << "Changing nothing" << endl << endl; 

    // Print again without changing anything 
    cout << "*p: " << *p << endl; 
    cout << "*x: " << *x << endl; 
    cout << "p: " << p << endl; 
    cout << "x: " << x << endl; 

    return 0; 
} 
+0

Да, я тоже это заметил, и если '* x' был инициализирован только тогда, когда он был использован, это имело бы смысл. Однако, как видно в objdump, он инициализируется до 456 перед печатью '* p' – sammko

+0

Кто-то остановил вопрос, не знаю почему !!! –

+2

Пользователи SO имеют эту странную (и неопределенную) тенденцию вниз по голосованию на любой вопрос, который сомневается в существовании святого УБ, напрасно называет имя Господа вашей богини УБ, или ложно свидетельствует против ее святой королевы UB 2.0. Я проголосовал за это, чтобы компенсировать, так как ОП явно и невинно не осознает тот факт, что он или она должен ** не ** публиковать вопросы UB на этом веб-сайте, а также обнажать гнев сообщества. –

1

Вы не собираетесь найти ответ в своем исходном коде или что компилятор делает для него, даже если производится сборка из компилятора.

Неопределенная реализация распределяет память C-runtime, которая уже скомпилировала двоичный код, связанный с вашим тестовым приложением. Когда вы вызываете new, библиотека времени выполнения решает, куда идет указатель. Нет никакой гарантии, что new/delete/new будет означать, что второе новое дает вам тот же адрес, это полностью зависит от реализации.

Если вы ДЕЙСТВИТЕЛЬНО хотите знать, тогда вам нужно создать полный исходный код, включая исходный код для нового, а затем прочитать, как он реализован и/или пройти через него в отладчике, чтобы посмотреть, что происходит.