C, PHP, VB, .NET

Дневникът на Филип Петров


* Внимание с адреси към локални променливи

Публикувано на 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

При въпросната задача студентът много добре беше замаскирал действието и компилаторът не връщаше никакви предупреждения. Отне ми цяла вечер :)

 



4 коментара


  1. Ляля каза:

    интересно ми е как я е замаскирал?

  2. Здравко каза:

    Интересно какво точно се случва с програмния стек за да се прояви този бъг? Защо след като има създадена променлива в паметта, след заделянето на същия тип променлива (втора променлива в друга функция), първата бива „презаписана“? Според мен това не е бъг в програмата, а бъг в компилатора. В книгите, които съм чел за C, няма правило, което да указва избягването на такива случаи.

  3. Няма бъг – всичко си е нормално.

    След излизане от блока на дадена функция, всички локални променливи вече стават ненужни и се изтриват. Обаче нашият указател продължава да сочи към паметта, където е била тази променлива. Когато друга променлива запише нещо на този адрес, става това от горния пример.

    Няма бъг – просто е така. На С е наша отговорност да внимаваме.

  4. Иван каза:

    > След излизане от блока на дадена функция, всички локални променливи вече стават ненужни и се изтриват.

    Това не е вярно. Всички имплементации, за да са бързи, не нулират памет заета от локални променливи. С++ стандарта задължава компилатора да викне деструктора на всички локални променливи, но ако локалната променлива е примитивна, дори при нулеви оптимизации (-О0), е по бързо просто компилатора да декрементира фрейм/стак пойнтера. Затова и dummyFunc() всъщност прави „нещо“: тя записва стойноста 2 в полето от памет който ни е върнат от getval() тъй като стак фрейма на тази функция има локална променлива. Целият проблем е че getval() инвокира Undefined Behaviour: С++ стандарта забранява връщането на адрес на локална променлива, модерни компилатори (gcc, clang etc) биха принтирали warning за това.

Добави коментар

Адресът на електронната поща няма да се публикува


*