* Внимание с адреси към локални променливи
Публикувано на 28 ноември 2009 в раздел С/С++.
Съвсем наскоро ми се наложи да оправям много странен бъг в една програма, която ми я прати студент. Накратко - функция на С връщаше адрес към локална променлива. Програмата си работеше перфектно, но... до един момент, в който се записваше нещо ново в стека. Естествено кодът беше достатъчно объркан и зле документиран, а и аз се бях "облъчил" с Java и ми беше трудно да превключа на "вълна C". Радвам се обаче, че успях да открия грешката. Съставих и един примерен модел на това, от което трябва да се пазите много, когато работите с указатели:
#include "stdafx.h" #include "stdio.h" int* getval() { // създаваме локална променлива int x=1; // Връщаме адреса ѝ - това е проблема! // Такава грешка може да се направи например // когато правим някакви временни изчисления. return &x; } // Функция, която "не прави нищо" void dummyFunc() { int x=2; } int main() { int *p; p = getval(); printf ("*p = %d\n", *p); dummyFunc(); printf ("*p = %d\n", *p); return 0; }
Изпълнението на горният код "изненадващо" е:
*p = 1 *p = 2
Интересното е, че в горния пример компилаторът много добре се ориентира и връща warning:
warning C4172: returning address of local variable or temporary
При въпросната задача студентът много добре беше замаскирал действието. Отне ми цяла вечер :)
интересно ми е как я е замаскирал?
Интересно какво точно се случва с програмния стек за да се прояви този бъг? Защо след като има създадена променлива в паметта, след заделянето на същия тип променлива (втора променлива в друга функция), първата бива „презаписана“? Според мен това не е бъг в програмата, а бъг в компилатора. В книгите, които съм чел за C, няма правило, което да указва избягването на такива случаи.
Всички имплементации, за да са бързи, не нулират памет заета от локални променливи. С++ стандарта задължава компилатора да викне деструктора на всички локални променливи, но ако локалната променлива е примитивна, дори при нулеви оптимизации (-О0), е по бързо просто компилатора да декрементира фрейм/стак пойнтера. Затова и dummyFunc() всъщност прави "нещо": тя записва стойноста 2 в полето от памет който ни е върнат от getval() тъй като стак фрейма на тази функция има локална променлива. Целият проблем е че getval() инвокира Undefined Behaviour: С++ стандарта забранява връщането на адрес на локална променлива, модерни компилатори (gcc, clang etc) биха принтирали warning за това.