Справочник функций

Ваш аккаунт

Войти через: 
Забыли пароль?
Регистрация
Информацию о новых материалах можно получать и без регистрации:

Почтовая рассылка

Подписчиков: -1
Последний выпуск: 19.06.2015

Предупреждение при передаче адреса локальной переменной

27K
01 декабря 2013 года
mnanorn
78 / / 01.12.2013
Здравствуйте!

Столкнулся с теоретической проблемой: нужно реализовать функцию, возвращающую часть строки до символа-разделителя (префикс). Решил проблему так:
Код:
__cdecl str su_getPrefix(str sStr, str sSeparators)
{
    if(!su_strLen(sStr) || !su_strLen(sSeparators))
        return (str)NULL;

    uchar    acPart[su_strLen(sStr)];
    su_word   usStrIterator = 0;
    su_word   usSepIterator = 0;

    while(sStr[usStrIterator])
    {
        while(sSeparators[usSepIterator])
            if(sStr[usStrIterator] == sSeparators[usSepIterator])
            {
                acPart[usStrIterator] = '';
                return (str)acPart;
            }else
                usSepIterator++;

        acPart[usStrIterator] = sStr[usStrIterator];
        usStrIterator++;
        usSepIterator = 0;
    }

    return (str)NULL;
}
Получаю предупреждение:

 
Код:
warning: function returns address of local variable [enabled by default]
Примечание: компиляция происходит на MinGW с опцией --std=c99, так что динамический (безразмерный) массив допустим.
Примечание 2: функция su_strLen(sStr) возвращает длину sStr.

Не могу понять, почему? Ведь соглашение __cdecl указывает, что стек очищает вызывающая сторона. Определять глобальную переменную нельзя потому, что функция станет рекурсивно не безопасной. Возвращать результат, как измененный аргумент тоже нельзя: функция должна поддерживать конвейерную обработку.

Кто что думает? Спасибо за внимание.
27K
01 декабря 2013 года
mnanorn
78 / / 01.12.2013
Цитата: sadovoya
Соглашения нужны только для линковки библеотек на разных языках. Предположений, как именно компилятор (когда, в какой последовательности) рреализует свои действия по cdecl соглашению -- не следует делать. Управление стеком -- уровень ниже C/С++.

Красивого решения без изменения исходной строки для вашего примера пока не вижу.


Как заглушку вставил

Код:
su_uchar su_copyStr(su_str sStr, su_uchar aucCopyStr[])
{
    if(!su_strLen(sStr))
        return (su_str)NULL;

    su_word  wStrIterator = 0;

    while(sStr[wStrIterator])
        aucCopyStr[wStrIterator++] = sStr[wStrIterator];

    aucCopyStr[wStrIterator] = '�';
    return (su_uchar)TRUE;
}
Она скопирует строку во внешний массив и предотвратит ее потерю.

Цитата: sadovoya
Цитата:
Примечание: компиляция происходит на MinGW с опцией --std=c99, так что динамический (безразмерный) массив допустим.



Так ли это? Сомневаюсь, что это допустимо стандартом. Думаю, это расширение языка от GCC. Это не переносимо на другие компиляторы, лучше избегать. По-моему это ничем не отличается от выделить память new в ф-ции и просто delete[] в ней-же (уверенности нет). Тут ничего опасного нет. Опасно выделять в одном месте, освобождать - в другом. У вас же это приведет к delete[] массива прямо в ф-ции и его потере.


Да, это так, можете не сомневаться. Стандарту уже 14 лет, а K&R до сих пор жива (не без причин, конечно). О new / delete речи нет. Код пишется на стандартном Си.

Warning объясним вот чем: возвращается указатель, а не содержимое массива и компилятор опасается, что в программе будет висеть указатель (сохраненный) на не существующий участок памяти. К слову сказать: функция g_get_application_name из glib возвращает const g_char*, т.е. указатель на неизменяемую строку, хотя в параметрах у нее void. Строго говоря, сброс стека обычно происходит как

 
Код:
mov esp, ebp
Т.е., реально память не затирается. Но гарантии, что следующий вызов этого не сделает нет. Всем спасибо за помощь.
1
01 декабря 2013 года
kot_
7.3K / / 20.01.2000
ИМХО варнинг связан с этим:

 
Код:
return (str)NULL;
так как в результате приведения типов создается временный локальный строковый объект.
27K
01 декабря 2013 года
mnanorn
78 / / 01.12.2013
Цитата: kot_
ИМХО варнинг связан с этим:

 
Код:
return (str)NULL;
так как в результате приведения типов создается временный локальный строковый объект.


Нет, абсолютно. Warning связан с тем, что я возвращаю указатель на локальный динамический массив acPart (об этом можно судить по номеру строки. Простите, не привел).
Смысл вопроса-то вот в чем: да, локальный массив (хоть и динамический) находится в стеке, который будет сброшен после возврата из функции. НО! Ведь "ответ" функции возвращается тоже через стек. Следовательно, так как "ответ" возвращается через стек, и по правилам __cdecl стек очищает вызывающая сторона, логично предположить, что вызывающая сторона не станет этого делать до того, как использует возвращенное значение. Следовательно, массив в стеке должен остаться целым до момента использования "ответа", как я понимаю.
Да, в случае __stdcall возвращать указатель на локальную переменную не верно, так как стек сбросит вызываемая функция, и гарантии, что значение в стеке уцелеет нет (его может переписать следующий вызов). Но здесь я ведь явно задал тип вызова __cdecl. Что же еще сделать?..

326
01 декабря 2013 года
sadovoya
757 / / 19.11.2005
Думаю, что в C/С++ мы не должны делать предположений о том, как реализация компилятора освобождает стек. Просто не следует возвращать локальные адреса. Считайте, что при выходе они уничтожены независимо от соглашениях об вызовах. В ассемблере вы могли-бы стек разобрать, как хочется.

Лучше вернуть адрес относительно sStr (она не разрушается).

Вот пример законного возврата адреса:


 
Код:
//Соединение двух c-строк (нультерминир.)
//string_head должна быть достаточной для хранения результата
char* AppendString(char* string_head, char* string_tail) {
     int length;

     length = strlen(string_head);
     strcpy(string_head + length, string_tail);
     return string_head;
}
P.S. А cdecl и так идет по-умолчанию. И это в частности как-бы намек, что действия с выделением памяти делать в вызывающей программе, а ф-циям передавать адрес и размерность. Уничтожать в вызывающей программе.
27K
01 декабря 2013 года
mnanorn
78 / / 01.12.2013
Цитата: sadovoya
Думаю, что в C/С++ мы не должны делать предположений о том, как реализация компилятора освобождает стек. Просто не следует возвращать локальные адреса. Считайте, что при выходе они уничтожены независимо от соглашениях об вызовах. В ассемблере вы могли-бы стек разобрать, как хочется.

Лучше вернуть адрес относительно sStr (она не разрушается).


Что Вы имеете ввиду под "вернуть адрес относительно sStr?" Прошу раскрыть тему, если можно. Если Вы имеете ввиду переписать строку sStr, то нет: исходная строка не должна измениться.
Насчет предположений, позволю себе с Вами не согласиться. Порядок cdecl определен стандартами C89 и C99. Это совсем не предположения. Если не учитывать порядок работы со стеком, создавать библиотеки и делать линковку из объектных файлов разных разработчиков станет сложно. ИМХО.

326
01 декабря 2013 года
sadovoya
757 / / 19.11.2005
Соглашения нужны только для линковки библеотек на разных языках. Предположений, как именно компилятор (когда, в какой последовательности) рреализует свои действия по cdecl соглашению -- не следует делать. Управление стеком -- уровень ниже C/С++.

Красивого решения без изменения исходной строки для вашего примера пока не вижу.
326
01 декабря 2013 года
sadovoya
757 / / 19.11.2005
Цитата:
Примечание: компиляция происходит на MinGW с опцией --std=c99, так что динамический (безразмерный) массив допустим.



Так ли это? Сомневаюсь, что это допустимо стандартом. Думаю, это расширение языка от GCC. Это не переносимо на другие компиляторы, лучше избегать. По-моему это ничем не отличается от выделить память new в ф-ции и просто delete[] в ней-же (уверенности нет). Тут ничего опасного нет. Опасно выделять в одном месте, освобождать - в другом. У вас же это приведет к delete[] массива прямо в ф-ции и его потере.

326
01 декабря 2013 года
sadovoya
757 / / 19.11.2005
Красивое решение -- только то, которое вы хотели. К сожалению не реально. Остается либо полностью менять идею, либо возвращать кусок строки, портя исходную. Т.е. нультерминатор вписывать в место разделителя и return sStr. Забудьте о выделении буфера внутри ф-ции и его использовании за ее пределами.
326
01 декабря 2013 года
sadovoya
757 / / 19.11.2005
Спасибо за информацию. Действительно в C99 появились на законном основании массивы переменной длины. Насчет new / delete я конечно ляпнул, но все-равно как кроме средст выделения/освобождения памяти типа malloc/free компилятор их поддерживает? Ведь на этапе компиляции размер может быть не известен.

В С++ тоже не понятно, что делать с массивами переменной длины. Может, кто знает появились они в стандарте и когда?
27K
02 декабря 2013 года
mnanorn
78 / / 01.12.2013
Цитата: sadovoya
Спасибо за информацию. Действительно в C99 появились на законном основании массивы переменной длины. Насчет new / delete я конечно ляпнул, но все-равно как кроме средст выделения/освобождения памяти типа malloc/free компилятор их поддерживает? Ведь на этапе компиляции размер может быть не известен.

В С++ тоже не понятно, что делать с массивами переменной длины. Может, кто знает появились они в стандарте и когда?


Полагаю, поддержка динамических массивов реализована в C RTL с помощью того же malloc() / free(). Просто теперь их вызов стал не явным для программиста. Я даже насчет free() не уверен, т.к. есть штатный способ увеличить длуну массива, но нет способа ее уменьшить (массив изменяется только вверх).

Последний известный мне стандарт C++ - C++11. И, насколько мне известно, штатных способов управления памятью, кроме new() / free() в нем нет.
C++11, Википедия

К слову, у C тоже есть стандарт от 2011 года:
C11, Википедия

Знаете кого-то, кто может ответить? Поделитесь с ним ссылкой.

Ваш ответ

Реклама на сайте | Обмен ссылками | Ссылки | Экспорт (RSS) | Контакты
Добавить статью | Добавить исходник | Добавить хостинг-провайдера | Добавить сайт в каталог