Согласно этому фильму (около 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 <printf@plt>
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
c
gcc
memory-management
Эльяшив
источник
источник
Ответы:
Да, да, это неопределенное поведение , потому что вы используете неинициализированную переменную 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 -m32 -O0 test.c
(Оптимизация отключена). Когда я запускаю это, он печатает мусор.Глядя на
$ objdump -Mintel -d ./a.out
:Мы видим
B
, что компилятор зарезервировал 0x10 байт пространства стека и инициализировал нашуint a
переменную на[ebp-0x4]
5.В
A
Тем не менее, компилятор помещенint a
в[ebp-0xc]
. Итак, в этом случае наши локальные переменные не оказались в одном месте! Добавляяprintf()
вызов ,A
а также вызовет кадры стека дляA
иB
быть идентичными, и печати55
.источник
Это неопределенное поведение . Неинициализированная локальная переменная имеет неопределенное значение, и ее использование приведет к неопределенному поведению.
источник
{ int uninit; &uninit; printf("%d\n", uninit); }
все еще имеет неопределенное поведение. С другой стороны, вы можете рассматривать любой объект как массивunsigned char
; это то, что вы имели в виду?Следует помнить одну важную вещь - никогда не полагайтесь на что-то подобное и никогда не используйте это в реальном коде! Это просто интересная штука (что даже не всегда так), а не особенность или что-то в этом роде. Представьте, что вы пытаетесь найти ошибку, вызванную такой «особенностью» - кошмаром.
Кстати. - C и C ++ полны таких «функций», вот ОТЛИЧНОЕ слайд-шоу об этом: http://www.slideshare.net/olvemaudal/deep-c Итак, если вы хотите увидеть больше похожих «функций», поймите, что под капотом и как это работает, просто посмотрите это слайд-шоу - вы не пожалеете, и я уверен, что даже большинство опытных программистов на c / c ++ могут многому научиться из этого.
источник
В функции
A
переменнаяa
не инициализируется, вывод ее значения приводит к неопределенному поведению.В некоторых компиляторах переменные
a
inA
иa
inB
имеют один и тот же адрес, поэтому он может печатать5
, но опять же, вы не можете полагаться на неопределенное поведение.источник
s machine will be the same depends on the assembly generated by the compiler. As @JonathonReinhart pointed out the call to
оптимизированы результаты на исходном плакате B () `.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 Печать неинициализированной переменной приводит к неопределенному поведению.
Раздел 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
флага Using,gcc-O0
вы можете получить результат 5.Но это совсем не хорошая идея, никогда не используйте это в производственном коде.
-Wuninitialized
это одно из ценных предупреждений. Вы должны принять это во внимание. Вы не должны ни отключать, ни пропускать это предупреждение, которое приводит к огромному ущербу в производственной среде, например, вызывает сбои во время работы демонов.Edit2
Слайды Deep C объяснили, почему результат 5 / мусор. Добавление этой информации из этих слайдов с небольшими изменениями, чтобы сделать этот ответ немного более эффективным.
$ gcc -O0 file.c && ./a.out 5
Возможно, у этого компилятора есть набор именованных переменных, которые он использует повторно. Например, переменная a была использована и выпущена
B()
, тогда, когда ейA()
потребуются целочисленные имена,a
она получит переменную, которая получит то же место в памяти. Если вы переименуете переменнуюB()
, скажемb
, в, то я не думаю, что вы получите5
.Когда срабатывает оптимизатор, может произойти множество вещей. В этом случае я предполагаю, что вызов
B()
можно пропустить, поскольку он не имеет побочных эффектов. Кроме того, я не удивлюсь, если онA()
будет встроенmain()
, то есть без вызова функции. (Но посколькуA ()
есть видимость компоновщика, объектный код для функции все равно должен быть создан на тот случай, если другой объектный файл захочет связать с функцией). В любом случае, я подозреваю, что напечатанное значение будет чем-то другим, если вы оптимизируете код.gcc -O file.c && ./a.out 1606415608
Фигня!
источник