Конфликт между учебником Стэнфорда и GCC
В соответствии с этим фильмом (около минуты 38), если у меня есть две функции с одинаковыми локальными варами, они будут использовать одно и то же пространство. Поэтому следующая программа должна печатать 5
. Составив его с результатами gcc
-1218960859
. почему?
Программа:
#include <stdio.h>
void A()
{
int a;
printf("%i",a);
}
void B()
{
int a;
a = 5;
}
int main()
{
B();
A();
return 0;
}
по запросу, вот вывод от дизассемблера:
0804840c <A>:
804840c: 55 push ebp
804840d: 89 e5 mov ebp,esp
804840f: 83 ec 28 sub esp,0x28
8048412: 8b 45 f4 mov eax,DWORD PTR [ebp-0xc]
8048415: 89 44 24 04 mov DWORD PTR [esp+0x4],eax
8048419: c7 04 24 e8 84 04 08 mov DWORD PTR [esp],0x80484e8
8048420: e8 cb fe ff ff call 80482f0 <[email protected]>
8048425: c9 leave
8048426: c3 ret
08048427 <B>:
8048427: 55 push ebp
8048428: 89 e5 mov ebp,esp
804842a: 83 ec 10 sub esp,0x10
804842d: c7 45 fc 05 00 00 00 mov DWORD PTR [ebp-0x4],0x5
8048434: c9 leave
8048435: c3 ret
08048436 <main>:
8048436: 55 push ebp
8048437: 89 e5 mov ebp,esp
8048439: 83 e4 f0 and esp,0xfffffff0
804843c: e8 e6 ff ff ff call 8048427 <B>
8048441: e8 c6 ff ff ff call 804840c <A>
8048446: b8 00 00 00 00 mov eax,0x0
804844b: c9 leave
804844c: c3 ret
804844d: 66 90 xchg ax,ax
804844f: 90 nop
Ответы
Ответ 1
Да, да, это поведение undefined, потому что вы используете переменную uninitialized 1.
Однако на архитектуре x86 2 этот эксперимент должен работать. Значение не "стирается" из стека, и поскольку оно не инициализируется в B()
, это же значение все равно должно присутствовать при условии, что кадры стека идентичны.
Я бы догадался, что, поскольку int a
не используется внутри void B()
, компилятор оптимизировал этот код, а 5 никогда не записывался в это место в стеке. Попробуйте добавить printf
в B()
, но это может сработать.
Кроме того, флаги компилятора, а именно уровень оптимизации, вероятно, повлияют и на этот эксперимент. Попробуйте отключить оптимизацию, передав -O0
в gcc.
Редактирование: я просто скомпилировал ваш код с помощью gcc -O0
(64-разрядный), и действительно, программа печатает 5, как ожидал бы знакомый с стеком вызовов. Фактически, он работал даже без -O0
. 32-битная сборка может вести себя по-разному.
Отказ от ответственности: никогда, никогда не используйте что-то вроде этого в "реальном" коде!
1 - Здесь обсуждается о том, является ли это официально "UB" или просто непредсказуемым.
2 - Также x64 и, возможно, каждая другая архитектура, использующая стек вызовов (по крайней мере, с MMU)
Позвольте взглянуть на причину, почему это не сработало. Это лучше всего видно в 32 бит, поэтому я буду компилироваться с помощью -m32
.
$ gcc --version
gcc (GCC) 4.7.2 20120921 (Red Hat 4.7.2-2)
Я скомпилирован с $ gcc -m32 -O0 test.c
(оптимизация отключена). Когда я запускаю это, он печатает мусор.
Глядя на $ objdump -Mintel -d ./a.out
:
080483ec <A>:
80483ec: 55 push ebp
80483ed: 89 e5 mov ebp,esp
80483ef: 83 ec 28 sub esp,0x28
80483f2: 8b 45 f4 mov eax,DWORD PTR [ebp-0xc]
80483f5: 89 44 24 04 mov DWORD PTR [esp+0x4],eax
80483f9: c7 04 24 c4 84 04 08 mov DWORD PTR [esp],0x80484c4
8048400: e8 cb fe ff ff call 80482d0 <[email protected]>
8048405: c9 leave
8048406: c3 ret
08048407 <B>:
8048407: 55 push ebp
8048408: 89 e5 mov ebp,esp
804840a: 83 ec 10 sub esp,0x10
804840d: c7 45 fc 05 00 00 00 mov DWORD PTR [ebp-0x4],0x5
8048414: c9 leave
8048415: c3 ret
Мы видим, что в B
компилятор зарезервировал 0x10 байт пространства стека и инициализировал нашу переменную int a
в [ebp-0x4]
до 5.
В A
компилятор разместил int a
в [ebp-0xc]
. Поэтому в этом случае наши локальные переменные не оказались в одном месте! Добавив вызов printf()
в A
, также будут идентичны кадры стека для A
и B
и напечатать 55
.
Ответ 2
Это поведение undefined. Неинициализированная локальная переменная имеет неопределенное значение, и ее использование приведет к поведению undefined.
Ответ 3
Одна важная вещь, которую нужно запомнить - не никогда не полагаться на что-то подобное и никогда использовать это в реальном коде! Это просто интересная вещь (которая даже не всегда верна), а не функция или что-то в этом роде. Представьте, что вы пытаетесь найти ошибку, вызванную такой "особенностью" - кошмаром.
Btw. - C и С++ полны таких "функций", вот слайд-шоу GREAT:
http://www.slideshare.net/olvemaudal/deep-c Итак, если вы хотите увидеть более похожие "функции", поймите, что под капотом и как он работает, просто смотрите это слайд-шоу - вы не пожалеете и я Я уверен, что даже большинство опытных программистов c/С++ могут многому научиться из этого.
Ответ 4
В функции A
переменная A
не инициализируется, печать ее значения приводит к поведению undefined.
В некотором компиляторе переменная A
в A
и A
в B
находится в одном и том же адресе, поэтому она может печатать 5
, но вы не можете полагаться на undefined поведение.
Ответ 5
Скомпилируйте свой код с помощью gcc -Wall filename.c
. Вы увидите эти предупреждения.
In function 'B':
11:9: warning: variable 'a' set but not used [-Wunused-but-set-variable]
In function 'A':
6:11: warning: 'a' is used uninitialized in this function [-Wuninitialized]
В c Печать неинициализированной переменной приводит к поведению Undefined.
Раздел 6.7.8 Инициализация стандарта C99 гласит:
Если объект с автоматической продолжительностью хранения не инициализируется явно, его значение неопределенно. Если объект, который имеет статическую продолжительность хранения, не инициализируется явно, то:
— if it has pointer type, it is initialized to a null pointer;
— if it has arithmetic type, it is initialized to (positive or unsigned) zero;
— if it is an aggregate, every member is initialized (recursively) according to these rules;
— if it is a union, the first named member is initialized (recursively) according to these rules.
Edit1
Как @Jonathon Reinhart Если вы отключите оптимизацию, используя флаг -O
gcc-O0
, вы можете получить вывод 5.
Но это не совсем хорошая идея, никогда не используйте ее в производственном коде.
-Wuninitialized
Это одно из ценных предупреждений. Вы должны учитывать это. Вы не должны либо отключать, либо пропускать это предупреждение, которое приводит к огромному ущербу в производстве, например, к сбоям во время работы демонов.
Edit2
Deep C слайды объясняют, почему результат 5/garbage. Добавление этой информации с этих слайдов с незначительными изменениями, чтобы сделать этот ответ более эффективным.
Случай 1: без оптимизации
$ gcc -O0 file.c && ./a.out
5
Возможно, этот компилятор имеет пул
именованные переменные, которые он использует повторно. Например
переменная a была использована и выпущена в
B()
, тогда, когда A()
требуется
целочисленные имена a
, он получит
переменная получит одну и ту же память
место нахождения. Если вы переименуете переменную
в B()
до, скажем b
, тогда я не думаю
вы получите 5
.
Случай 2: с оптимизацией
При запуске оптимизатора может произойти много чего.
в этом случае я бы предположил, что вызов B()
можно пропустить как
он не имеет никаких побочных эффектов. Кроме того, я не удивлюсь
если A()
встроен в main()
, т.е. нет вызова функции. (Но поскольку A
()
имеет видимость линкера, объектный код для функции должен все же
создаваться на случай, если другой объектный файл хочет связать
функция). В любом случае, я подозреваю, что напечатанное значение будет
что-то еще, если вы оптимизируете код.
gcc -O file.c && ./a.out
1606415608
Garbage!