Здравствуйте, Carc, Вы писали:
Pzz>>Никому он ничего не должен. C>Ну-у-у, началось словоблудие знакомое… Кто спорил то!?! Попытки вернуть адрес локальной переменной из функции и так понятно к чему приводит. Я о другом вообще говорил! Что в данном конкретном примере, при таком соглашении вызовов cdecl, по вернутому таким некошерным способом указателе будут запросто тем не менее лежать валидные данные.
Сразу видно, что ты никогда не программировал в среде, которая использует твой текущий стек для обработки прерываний.
Это совершенно не нарушает сишную calling convention, но данные, которые "над стеком" могут быть испорчены в любой момент.
Кстати, тоже самое может произойти и в привычной тебе среде, если компилятор вставляет служебные вызовы фукнций для проверки границ массивов, для профилировки и т.п.
Carc:
J>>как вернуть значение локальной переменной szTmp? C>
C>return &szTmp[0];
C>
C>Ибо, раз два плюса — то по умолчанию соглашение вызовов cdecl, то бишь освобождает стек вызывающая функция. C>Соответственно, где-то в вызывающей функции строка вида будет валидна C>
C>const char* const pszResult=…Sum();
C>
C>Т.к. после const char* const pszResult=… сам массив szTmp в вызываемой функции все еще будет существовать. В том смысле, что вызываемая уже отработала конечно, но вот добро ее на стеке все еще в полном порядочке лежит.
Это только один из возможных сценариев. Если компилятор начнёт оптимизировать код, результат может получиться совсем другим. Например, вызовы strcpy и strcat внутри Sum могут быть выброшены оптимизатором за ненадобностью, т.к. в этой функции изменённый массив szTmp нигде больше не используется, а за её пределами обращение к уничтоженному объекту уже незаконно (оптимизатору нет никакого дела до хаков, вызывающих undefined behavior, его задача действовать в рамках "as-if" rule, и выбрасывание strcpy вместе с strcat оное правило никак не нарушает). Встраивание самой Sum в код вызывающей функции тоже может привести к интересным результатам.
C>Я где-то увидел такое решение, и чуть не припух. Ибо проект был солидный, и парни там грамотные. Очень долго въезжал, что к чему. Потом стало ясно. C>В общем, такие решения использовать можно.
В программе, которая должна работать надёжно, однозначно нельзя. При обнаружении такого кода его надо фиксить ASAP (даже если пока он "вроде бы работает").
Вариант 1 — все работает! Благодарю!
Получить значение нужно всего 1 раз, т.к. следующего раза, возможно, уже не будет, а держать лишнюю переменную под промежуточное значение нет желания.
Здравствуйте, Jumangee, Вы писали:
J>Добрый день! J>Вопрос: J>char * CFooDlg::Sum() J>{ J> char szTmp[200]; J> strcpy(szTmp, m_szVal1); J> strcat(szTmp, m_szVal2);
J> return значение_szTmp; J>}
J>как вернуть значение локальной переменной szTmp?
return &szTmp[0];
Ибо, раз два плюса — то по умолчанию соглашение вызовов cdecl, то бишь освобождает стек вызывающая функция.
Соответственно, где-то в вызывающей функции строка вида будет валидна
const char* const pszResult=…Sum();
Т.к. после const char* const pszResult=… сам массив szTmp в вызываемой функции все еще будет существовать. В том смысле, что вызываемая уже отработала конечно, но вот добро ее на стеке все еще в полном порядочке лежит.
1) Эффективно!?! О да-а-а!
2) Опасно? Нисколько. Ну разве что иногда выстреливая в ногу может случайно снести и голову такой вызов.
3) Поддерживаемо? О-да! Еще как причем — один раз заложитесь на такое документированное, но неочевидное поведение и будете просто обязаны постоянно держать в голове это, и поддерживать завсегда. Иначе таки снесет и всю голову приложению.
4) Переносимо! Да запросто. Но есть нюанззз (ц). Не дай бог какая "пьянь" с какого-нить перепугу сменит тоже соглашение вызовов. И тут начнется.
5) Отлаживаемо! Легко и непринужденно. Только если хоть что-нибудь из вышеперечисленного случится, то полный привет. Докопаетесь вы до причины такой баги году так эдак 2многонулей_какая_цифра. Т.е не_приведи_оспади
Я где-то увидел такое решение, и чуть не припух. Ибо проект был солидный, и парни там грамотные. Очень долго въезжал, что к чему. Потом стало ясно.
В общем, такие решения использовать можно. Но нужно четко понимать себе их последствия и ограничения их использования.
В противном случае, все стандартные правила вроде "кто буфер выделял, тот и освобождает". Или возвращать какой-то прокси-объект вроде std::array ну или свой "лесапет" прикрутить. Тут уж кто во что горазд.
А зачем все так сложно с этой переменной s, только чтобы не переписывать использование?
БЫЛО
К>// плохо №1
К>std::string Sum();
К>void Work() {
К> char* s = Sum().data(); // чтобы ниже переписывать по-минимуму
К> // прямо с этого места s невалидно!
К> . . .
К> printf("%s", s);
К>}
А нужно только поменять имя переменной получающей результат Sum(), и тут же строкой ниже привести ее char* s= в зависимости от типа переменной, в которой результат Sum()
// может и не хорошо, но уже неплохо
std::string Sum();
void Work() {
std::string TEMP_VAR = Sum().data(); // чтобы ниже переписывать по-минимуму
//а вот тут уже char* s=TEMP_VAR.c_str();//приводим ее к нужному нам имени и нужным способом, в том же случае с CString просто переписываем эту строку на "char* s=(LPCTSTR)TEMP_VAR";
// и вот прямо с этого места нам больше вообще ничего трогать и не потребуется…
. . .
printf("%s", s);
Не проще ли?
Изменения все равно понадобятся, в зависимости что у нас используется char* s, std::string, MFC|WTL::CString
Но по крайней мере изменения будут минимальны, а главное локализованы в одном месте — буквально в 2-ух строчках, а не размазаны по коду ниже…
Здравствуйте, Carc, Вы писали:
C>Т.к. после const char* const pszResult=… сам массив szTmp в вызываемой функции все еще будет существовать. В том смысле, что вызываемая уже отработала конечно, но вот добро ее на стеке все еще в полном порядочке лежит.
Добавь static и добро будет лежать где надо всю программу. Если оно считается лишь один раз за время жизни программы, то катастрофы не случится.
Нет такой подлости и мерзости, на которую бы не пошёл gcc ради бессмысленных 5% скорости в никому не нужном синтетическом тесте
C>Согласен, но в том и "фишка" что указатель на локальную переменную после выхода из вызываемой должен остаться валидным в вызывающей функции... Ну, понятно, это все равно "хак".
А чего бы ему оказаться не валидным, если это указатель на стек. Вот данные по этому указателю могут угробиться при первой же вызванной функции. Это не хак, это русская рулетка.
Здравствуйте, Jumangee, Вы писали:
J>Добрый день! J>Вопрос: J>char * CFooDlg::Sum() J>{ J> char szTmp[200]; J> strcpy(szTmp, m_szVal1); J> strcat(szTmp, m_szVal2);
J> return значение_szTmp; J>}
J>как вернуть значение локальной переменной szTmp?
1) Можно вернуть std::string, раз уж программа написана на C++
2) Можно объявить szTmp, как static. Проблема в том, что следующий вызов функции подменит предыдущее содержимое этого буфера, и если кто-то где-то все еще использует старое значение, этот кто-то сильно удивится.
3) Можно при каждом вызове выделять новый буфер с помощью оператора new или функции malloc. Тот, кто вызывает функцию, возвращающую данные таким образом, должен не забывать их освобождать
4) Буфер может предоставлять вызывающая сторона.
P.S. Как бы то ни было, при использованнии strcpy, strcat и т.п. надо быть уверенным, что результат поместится в выходной буфер (или проверять это заранее, или в самой функции, или использовать более безопасные strncpy/strncar)
Здравствуйте, Carc, Вы писали:
C>Ибо, раз два плюса — то по умолчанию соглашение вызовов cdecl, то бишь освобождает стек вызывающая функция. C>Соответственно, где-то в вызывающей функции строка вида будет валидна
Вызывающая функция освобождает стек только от того, что она сама туда напихала. Но не от того, что туда напихала вызываемая функция.
Здравствуйте, Pzz, Вы писали:
Pzz>Вызывающая функция освобождает стек только от того, что она сама туда напихала. Но не от того, что туда напихала вызываемая функция.
Согласен, но в том и "фишка" что указатель на локальную переменную после выхода из вызываемой должен остаться валидным в вызывающей функции... Ну, понятно, это все равно "хак".
Здравствуйте, Carc, Вы писали:
Pzz>>Вызывающая функция освобождает стек только от того, что она сама туда напихала. Но не от того, что туда напихала вызываемая функция. C>Согласен, но в том и "фишка" что указатель на локальную переменную после выхода из вызываемой должен остаться валидным в вызывающей функции... Ну, понятно, это все равно "хак".
Здравствуйте, Somescout, Вы писали:
Pzz>>2) Можно объявить szTmp, как static. Проблема в том, что следующий вызов функции подменит предыдущее содержимое этого буфера, и если кто-то где-то все еще использует старое значение, этот кто-то сильно удивится.
S>По-моему такое не стоит советовать даже в шутку. Имхо, но это эталонный выстрел в ногу.
Совершенно не обязательно. Такое решение плохо, так сказать, масштабируется, но масштабирование не всегда бывает нужно. Так можно делать, если хорошо понимать, какие это накладывает ограничения, и как на эти ограничения случайно не наступить.
//////////////////
Вопрос решился иным способом.
Возвращаем из функции количество символов в релультирующей строке (как признак для дальнейшего поведения), а результирующую строку выделили отдельным членом класса.
Спасибо за комментарии.
TB>Добавь static и добро будет лежать где надо всю программу. Если оно считается лишь один раз за время жизни программы, то катастрофы не случится.
Смотря куда-добавить… + нужно понимать, что можно словить очень неочевидные проблемы уже в release, в определенных случаях. Ну, например, когда какой-нить кодо-липосакцией занимаемся и отцепляем си-рантайм.
Опять же порядок инициализации static-member недокументирован.
Хотя здесь, в конкретном этом примере, можно конечно и сделать в вызываемой функции что-нить вида static char szTemp[20]={0}.
В общем, готового рецепта на все случаи все равно нет. В каждом есть свои нюансы.
А так, static дело архиполезное, и архинужное. Сам пользуюсь частенько.
SD>А чего бы ему оказаться не валидным, если это указатель на стек. Вот данные по этому указателю могут угробиться при первой же вызванной функции. Это не хак, это русская рулетка.
Я про данные и говорил. Но то что "рулетка", я полностью согласен.
Здравствуйте, Pzz, Вы писали:
Pzz>Здравствуйте, Carc, Вы писали:
Pzz>>>Вызывающая функция освобождает стек только от того, что она сама туда напихала. Но не от того, что туда напихала вызываемая функция. C>>Согласен, но в том и "фишка" что указатель на локальную переменную после выхода из вызываемой дол
жен остаться валидным в вызывающей функции... Ну, понятно, это все равно "хак".
Pzz>Никому он ничего не должен.
Ну-у-у, началось словоблудие знакомое… Кто спорил то!?! Попытки вернуть адрес локальной переменной из функции и так понятно к чему приводит. Я о другом вообще говорил! Что в данном конкретном примере, при таком соглашении вызовов cdecl, по вернутому таким некошерным способом указателе будут запросто тем не менее лежать валидные данные.
И только всего! Это все, что я хотел сказать. Что с ним делать, с таким "некошерно" вёрнутым указателем, решать вызывающей функции. Ежу же понятно, что нужно сразу данные по нему куда-то "скопистить".
Такой же вариант бы сработал
char* const p=Sum();//вот тут мы получили этот абсолютно неверный указатель
//но его сразу нужно забрать, а не потом когда-нибудьif (NULL(nullptr) == p)
throw"Идите в *опу"; //т.е. если указатель не валидный, дальше вообще никаких переговоров
//или сразу его забираем себеconst std::string wowPtr(p);//все - забрали указатель
Если разговор в стиле топик-стартера, то ответ простой. Никак. Так нельзя. Нельзя использовать адрес локальной переменной после выхода из функции.
Если разговор как надо? Опять же ответ простой. Поиск по КЫВТ. 100 раз обсуждали. Подходов тыщи. А по сути их всего два. Остальных нет. Я не спроста написал, в первом же посте, что тот пример, который я показал, работать-то будет. Но так никогдане стоит делать.
C>//или сразу его забираем себе
C>const std::string wowPtr(p);//все - забрали указатель
C>
Это не "все забрали", а "все, приплыли". Указатель-то забрали, а сама переменная wowPtr вполне вероятно на месте данных и окажется, и перетрет строку своей инициализацией.
Здравствуйте, Pzz, Вы писали:
Pzz>2) Можно объявить szTmp, как static. Проблема в том, что следующий вызов функции подменит предыдущее содержимое этого буфера, и если кто-то где-то все еще использует старое значение, этот кто-то сильно удивится.
По-моему такое не стоит советовать даже в шутку. Имхо, но это эталонный выстрел в ногу.
Здравствуйте, Pzz, Вы писали:
Pzz>Здравствуйте, Carc, Вы писали:
Pzz>Сразу видно, что ты никогда не программировал в среде, которая использует твой текущий стек для обработки прерываний.
Pzz>Это совершенно не нарушает сишную calling convention, но данные, которые "над стеком" могут быть испорчены в любой момент.
Pzz>Кстати, тоже самое может произойти и в привычной тебе среде, если компилятор вставляет служебные вызовы фукнций для проверки границ массивов, для профилировки и т.п.
Pzz>Не умничай, короче, целее будешь
Ты о себе?
Именно выше перечисленные варианты событий я и имел ввиду (служебные вызовы).
Цитата из моего первого поста
Я где-то увидел такое решение, и чуть не припух. Ибо проект был солидный, и парни там грамотные. Очень долго въезжал, что к чему. Потом стало ясно.
Я про тот случай и говорил. Что очень удивился вообще такому решению. И что оно вообще может работать. Это только информация к размышлению топик-стартеру. И там же первом посте написал к чему приводит использование таких "оптимизаций"
Здравствуйте, Pzz, Вы писали:
Pzz>Совершенно не обязательно. Такое решение плохо, так сказать, масштабируется, но масштабирование не всегда бывает нужно. Так можно делать, если хорошо понимать, какие это накладывает ограничения, и как на эти ограничения случайно не наступить.
Именно для целей возврата изменяемого значения? Если речь о единожды вычисляемой константе — вопросов нет, а вот возвращать результат, который может быть изменён в процессе работы программы — закладывать мину в код.
ЗЫ. Хотя наверно для устройств с ограниченными ресурсами это нормальный вариант.
Здравствуйте, Jumangee, Вы писали:
J>Вариант 1 — все работает! Благодарю! J>Получить значение нужно всего 1 раз, т.к. следующего раза, возможно, уже не будет, а держать лишнюю переменную под промежуточное значение нет желания.
На всякий случай: главное, чтобы не накосячить по месту использования.
// былоchar* Sum(); // проблемы, как вернуть строку...void Work() {
char* s = Sum();
. . .
printf("%s", s);
}
// плохо №1
std::string Sum();
void Work() {
char* s = Sum().data(); // чтобы ниже переписывать по-минимуму
// прямо с этого места s невалидно!
. . .
printf("%s", s);
}
// плохо №2
std::string Sum();
void Work() {
std::string s = Sum(); // что получили, то и запомнили, хорошо
. . .
printf("%s", s); // передали в printf мусор (объект string устроен иначе, чем голый указатель)
}
// хорошо №1
std::string Sum();
void Work() {
std::string s = Sum();
. . .
printf("%s", s.c_str()); // извлекли указатель правильным способом
}
// хорошо №2
CStringA Sum(); // у ATL/MFC есть свои классы строкvoid Work() {
CStringA s = Sum();
. . .
printf("%s", s); // которые сделаны специально совместимыми для использования в сишных функциях
}
К>// хорошо №2
К>CStringA Sum(); // у ATL/MFC есть свои классы строк
К>void Work() {
К> CStringA s = Sum();
К> . . .
К> printf("%s", s); // которые сделаны специально совместимыми для использования в сишных функциях
К>}
К>
несмотря на поддержку в конкретном компиляторе, возникает соблазн использовать такое повсюду (писать свои такие же классы, затем писать так в гцц и тд)
в целом, плохая практика имхо