четверг, октября 05, 2006

Указатели как параметры функции. Динамическая память

Как и в предыдущей статье, поводом для размышления послужила тема форума. Представьте себе, что у вас есть строка, или некоторый буфер, размеры которого вам заранее неизвестны и в ходе программы могут меняться. Первое, что приходит на ум, - это конечно же динамическая память.

char*str = new char[size];

А теперь представьте, что ваша строка объявлена на некотором уровне и в процессе работы программы передается как указатель в некоторую функцию. В этой функции, скажем, как пример, происходит перевыделение динамической памяти. Это может выглядеть так:

void Realloc( char*s){
    delete s;
    s = new char[25];
    strcpy(s,"defgh");
}
void f()
{
    char*str = new char[20];
    strcpy(str,"abc");
    Realloc(str);
    delete str;
}

Давайте попытаемся проанализировать то, что здесь происходит. Сначала мы выделили динамически память под переменную str. Затем инициализировали ее, применив функцию strcpy. Дальше вызываем нашу функцию Realloc, причем заметьте строку мы передаем через указатель. Внутри функции происходит перевыделение памяти. Сначала мы освобождаем 20 байт старой памяти, потом выделяем 25 новой.

delete s;
s = new char[25];

Ничего криминального вроде бы не происходит. Переменная s соответствует переменной str.

Наконец после возврата из функции Realloc мы надеемся получить в переменной str строку defgh.

С этого момента и начинается интересное. То, что мы описали, будет работать под борландовскими компиляторами (тестировал на CBuilder6). А вот майкрософт иначе посмотрел на вещи (тестировал на Visual C++6). Если размер перевыделяемой памяти оставить прежним (бесмысленно - но для эксперимента), все будет работать. В нашем же примере размер изменился, и после выхода из функции Realloc в переменной str будет находиться всякая ерунда. Причину можно найти в отладчике - происходит смещение адреса указателя в памяти. Правда для меня остается загадкой, какое это вообще имеет отношение к изменениям в динамической памяти. Напоследок мы просто вылетаем из программы, что в общем уже и не удивительно, в момент выполнения

delete str;

7 комментариев:

sllh комментирует...

Гм.. странно, то ли лыжи... Но у меня на Builder'e 6-ом все отлично работает =(

sllh комментирует...

Все же лыжи едут -- лыжник не вполне в норме =( Я не углядел, что на Билдере как раз и работает =(
Проверил на 8ой студии -- там все ОК -- видимо MS немного подкорректировали компилятор =)

Yuri Volkov комментирует...

а так не пробовали? =)
void Realloc( char*&s){
delete s;
s = new char[25];
strcpy(s,"defgh");
}
void f()
{
char*str = new char[20];
strcpy(str,"abc");
Realloc(str);
delete str;
}

Давайте поподробнее разберемся что происходит в ВАШЕМ примере
1)
char*str = new char[20];
strcpy(str,"abc");
выделяем память под массив 20 char'ов и копируем в него строку "abc"
2)
Realloc(str);
вызываем функцию и передаем в нее КОПИЮ указателя на массив
3) Далее внутри Realloc
delete s;
поскольку s в Realloc() содержит тот же адрес что и str в а() то память удаляется.
4)
s = new char[25];
для s, время жизни которой ограничено Realloc выделяем память под массив из 25 char'ов и
strcpy(s,"defgh");
копируем в него строку "defgh"
Далее мы благополучно выходим из Realloc и получаем memleak. И поскольку указатель в Realloc передавался по ЗНАЧЕНИЮ то после выхода из Realloc str совсем не будет указывать на ту область памяти, которую выделили для 25 char'ов внутри Realloc() а будет указывать на СТАРУЮ область памяти, которую мы уже ОСВОБОДИЛИ.
5) После выходи из Realloc()
delete str;
Освоюождаем уже освобожденную память - получаем double free or corruption

Поэтому для подобных функций следует не забывать о способах возврата значений из функций (либо передавать параметры по ссылкам (указателям), либо делать return и).

Yuri Volkov комментирует...
Этот комментарий был удален автором.
Yuri Volkov комментирует...

кроме того если используется operator new[] то следует использовать operator delete[]. А если ВАШ пример компилировался каким-либо компилятором и работал так как Вы и ожидали - то это говорить лишь о том что компилятор выделял память по тому же адресу, что зависит от реализации компилятора, но тем не менее это баг в коде а не в компиляторе.

Анонимный комментирует...

Очень полезно

Анонимный комментирует...

Супер клас!!!