Re[6]: return char*
От: Pzz Россия https://github.com/alexpevzner
Дата: 11.03.17 19:26
Оценка: 1 (1) +3
Здравствуйте, Carc, Вы писали:

Pzz>>Никому он ничего не должен.

C>Ну-у-у, началось словоблудие знакомое… Кто спорил то!?! Попытки вернуть адрес локальной переменной из функции и так понятно к чему приводит. Я о другом вообще говорил! Что в данном конкретном примере, при таком соглашении вызовов cdecl, по вернутому таким некошерным способом указателе будут запросто тем не менее лежать валидные данные.

Сразу видно, что ты никогда не программировал в среде, которая использует твой текущий стек для обработки прерываний.

Это совершенно не нарушает сишную calling convention, но данные, которые "над стеком" могут быть испорчены в любой момент.

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

Не умничай, короче, целее будешь
Re[2]: return char*
От: N. I.  
Дата: 11.03.17 20:22
Оценка: +4
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 (даже если пока он "вроде бы работает").
Отредактировано 11.03.2017 20:31 N. I. . Предыдущая версия .
Re[2]: return char*
От: Jumangee Россия  
Дата: 11.03.17 12:24
Оценка: +1 :))
Вариант 1 — все работает! Благодарю!
Получить значение нужно всего 1 раз, т.к. следующего раза, возможно, уже не будет, а держать лишнюю переменную под промежуточное значение нет желания.
Re: return char*
От: Carc Россия http://www.amlpages.com/home.php
Дата: 11.03.17 12:32
Оценка: -3
Здравствуйте, 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 ну или свой "лесапет" прикрутить. Тут уж кто во что горазд.
Aml Pages Home
Re[4]: return char*
От: Carc Россия http://www.amlpages.com/home.php
Дата: 13.03.17 11:39
Оценка: 70 (1)
А зачем все так сложно с этой переменной 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-ух строчках, а не размазаны по коду ниже…
Aml Pages Home
Re[2]: return char*
От: T4r4sB Россия  
Дата: 11.03.17 12:47
Оценка: 1 (1)
Здравствуйте, Carc, Вы писали:

C>Т.к. после const char* const pszResult=… сам массив szTmp в вызываемой функции все еще будет существовать. В том смысле, что вызываемая уже отработала конечно, но вот добро ее на стеке все еще в полном порядочке лежит.


Добавь static и добро будет лежать где надо всю программу. Если оно считается лишь один раз за время жизни программы, то катастрофы не случится.
Нет такой подлости и мерзости, на которую бы не пошёл gcc ради бессмысленных 5% скорости в никому не нужном синтетическом тесте
Re[4]: return char*
От: std.denis Россия  
Дата: 11.03.17 14:44
Оценка: 1 (1)
C>Согласен, но в том и "фишка" что указатель на локальную переменную после выхода из вызываемой должен остаться валидным в вызывающей функции... Ну, понятно, это все равно "хак".

А чего бы ему оказаться не валидным, если это указатель на стек. Вот данные по этому указателю могут угробиться при первой же вызванной функции. Это не хак, это русская рулетка.
Re[8]: return char*
От: std.denis Россия  
Дата: 11.03.17 20:36
Оценка: 1 (1)
C>Я где-то увидел такое решение, и чуть не припух. Ибо проект был солидный, и парни там грамотные. Очень долго въезжал, что к чему. Потом стало ясно.

Похоже не до конца въехал приведенный код не работает, портит строку.
http://ideone.com/zwSsUP
Re: return char*
От: LaptevVV Россия  
Дата: 11.03.17 11:49
Оценка: -1
Сделай не локальный массив, а динамический.
Этот указатель и вернешь.
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Re: return char*
От: Pzz Россия https://github.com/alexpevzner
Дата: 11.03.17 11:58
Оценка: +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?


1) Можно вернуть std::string, раз уж программа написана на C++
2) Можно объявить szTmp, как static. Проблема в том, что следующий вызов функции подменит предыдущее содержимое этого буфера, и если кто-то где-то все еще использует старое значение, этот кто-то сильно удивится.
3) Можно при каждом вызове выделять новый буфер с помощью оператора new или функции malloc. Тот, кто вызывает функцию, возвращающую данные таким образом, должен не забывать их освобождать
4) Буфер может предоставлять вызывающая сторона.

P.S. Как бы то ни было, при использованнии strcpy, strcat и т.п. надо быть уверенным, что результат поместится в выходной буфер (или проверять это заранее, или в самой функции, или использовать более безопасные strncpy/strncar)
Re[2]: return char*
От: Pzz Россия https://github.com/alexpevzner
Дата: 11.03.17 14:30
Оценка: +1
Здравствуйте, Carc, Вы писали:

C>Ибо, раз два плюса — то по умолчанию соглашение вызовов cdecl, то бишь освобождает стек вызывающая функция.

C>Соответственно, где-то в вызывающей функции строка вида будет валидна

Вызывающая функция освобождает стек только от того, что она сама туда напихала. Но не от того, что туда напихала вызываемая функция.
Re[3]: return char*
От: Carc Россия http://www.amlpages.com/home.php
Дата: 11.03.17 14:37
Оценка: -1
Здравствуйте, Pzz, Вы писали:

Pzz>Вызывающая функция освобождает стек только от того, что она сама туда напихала. Но не от того, что туда напихала вызываемая функция.

Согласен, но в том и "фишка" что указатель на локальную переменную после выхода из вызываемой должен остаться валидным в вызывающей функции... Ну, понятно, это все равно "хак".
Aml Pages Home
Re[4]: return char*
От: Pzz Россия https://github.com/alexpevzner
Дата: 11.03.17 16:15
Оценка: -1
Здравствуйте, Carc, Вы писали:

Pzz>>Вызывающая функция освобождает стек только от того, что она сама туда напихала. Но не от того, что туда напихала вызываемая функция.

C>Согласен, но в том и "фишка" что указатель на локальную переменную после выхода из вызываемой должен остаться валидным в вызывающей функции... Ну, понятно, это все равно "хак".

Никому он ничего не должен.
Re[3]: return char*
От: Pzz Россия https://github.com/alexpevzner
Дата: 11.03.17 19:28
Оценка: +1
Здравствуйте, Somescout, Вы писали:

Pzz>>2) Можно объявить szTmp, как static. Проблема в том, что следующий вызов функции подменит предыдущее содержимое этого буфера, и если кто-то где-то все еще использует старое значение, этот кто-то сильно удивится.


S>По-моему такое не стоит советовать даже в шутку. Имхо, но это эталонный выстрел в ногу.


Совершенно не обязательно. Такое решение плохо, так сказать, масштабируется, но масштабирование не всегда бывает нужно. Так можно делать, если хорошо понимать, какие это накладывает ограничения, и как на эти ограничения случайно не наступить.
Re[7]: return char*
От: std.denis Россия  
Дата: 11.03.17 20:23
Оценка: +1
Pzz>Сразу видно, что ты никогда не программировал в среде, которая использует твой текущий стек для обработки прерываний.

да там и без прерываний хвост массива вполне потрётся при вызове конструктора и привет – возможный выбег за границу буфера
return char*
От: Jumangee Россия  
Дата: 11.03.17 11:35
Оценка:
Добрый день!
Вопрос:
char * CFooDlg::Sum()
{
char szTmp[200];
strcpy(szTmp, m_szVal1);
strcat(szTmp, m_szVal2);

return значение_szTmp;
}

как вернуть значение локальной переменной szTmp?

//////////////////
Вопрос решился иным способом.
Возвращаем из функции количество символов в релультирующей строке (как признак для дальнейшего поведения), а результирующую строку выделили отдельным членом класса.
Спасибо за комментарии.
Отредактировано 13.03.2017 15:00 Jumangee . Предыдущая версия .
Re: return char*
От: night beast СССР  
Дата: 11.03.17 11:49
Оценка:
Здравствуйте, Jumangee, Вы писали:

J>как вернуть значение локальной переменной szTmp?


std::array<char, 200>
Re[3]: return char*
От: Carc Россия http://www.amlpages.com/home.php
Дата: 11.03.17 13:11
Оценка:
Здравствуйте, T4r4sB, Вы писали:


TB>Добавь static и добро будет лежать где надо всю программу. Если оно считается лишь один раз за время жизни программы, то катастрофы не случится.

Смотря куда-добавить… + нужно понимать, что можно словить очень неочевидные проблемы уже в release, в определенных случаях. Ну, например, когда какой-нить кодо-липосакцией занимаемся и отцепляем си-рантайм.

Опять же порядок инициализации static-member недокументирован.

Хотя здесь, в конкретном этом примере, можно конечно и сделать в вызываемой функции что-нить вида static char szTemp[20]={0}.

В общем, готового рецепта на все случаи все равно нет. В каждом есть свои нюансы.
А так, static дело архиполезное, и архинужное. Сам пользуюсь частенько.
Aml Pages Home
Re[5]: return char*
От: Carc Россия http://www.amlpages.com/home.php
Дата: 11.03.17 15:06
Оценка:
Здравствуйте, std.denis, Вы писали:


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

Я про данные и говорил. Но то что "рулетка", я полностью согласен.
Aml Pages Home
Re[5]: return char*
От: Carc Россия http://www.amlpages.com/home.php
Дата: 11.03.17 18:30
Оценка:
Здравствуйте, Pzz, Вы писали:

Pzz>Здравствуйте, Carc, Вы писали:


Pzz>>>Вызывающая функция освобождает стек только от того, что она сама туда напихала. Но не от того, что туда напихала вызываемая функция.

C>>Согласен, но в том и "фишка" что указатель на локальную переменную после выхода из вызываемой дол
жен остаться валидным в вызывающей функции... Ну, понятно, это все равно "хак".

Pzz>Никому он ничего не должен.

Ну-у-у, началось словоблудие знакомое… Кто спорил то!?! Попытки вернуть адрес локальной переменной из функции и так понятно к чему приводит. Я о другом вообще говорил! Что в данном конкретном примере, при таком соглашении вызовов cdecl, по вернутому таким некошерным способом указателе будут запросто тем не менее лежать валидные данные.

И только всего! Это все, что я хотел сказать. Что с ним делать, с таким "некошерно" вёрнутым указателем, решать вызывающей функции. Ежу же понятно, что нужно сразу данные по нему куда-то "скопистить".

Такой же вариант бы сработал

char* const p=Sum();//вот тут мы получили этот абсолютно неверный указатель

//но его сразу нужно забрать, а не потом когда-нибудь
if (NULL(nullptr) == p)
    throw "Идите в *опу"; //т.е. если указатель не валидный, дальше вообще никаких переговоров

//или сразу его забираем себе
const std::string wowPtr(p);//все - забрали указатель


Если разговор в стиле топик-стартера, то ответ простой. Никак. Так нельзя. Нельзя использовать адрес локальной переменной после выхода из функции.

Если разговор как надо? Опять же ответ простой. Поиск по КЫВТ. 100 раз обсуждали. Подходов тыщи. А по сути их всего два. Остальных нет. Я не спроста написал, в первом же посте, что тот пример, который я показал, работать-то будет. Но так никогда не стоит делать.
Aml Pages Home
Отредактировано 11.03.2017 18:32 Carc . Предыдущая версия .
Re[6]: return char*
От: andrey.desman  
Дата: 11.03.17 18:42
Оценка:
Здравствуйте, Carc, Вы писали:

C>
C>//или сразу его забираем себе
C>const std::string wowPtr(p);//все - забрали указатель
C>


Это не "все забрали", а "все, приплыли". Указатель-то забрали, а сама переменная wowPtr вполне вероятно на месте данных и окажется, и перетрет строку своей инициализацией.
Re[2]: return char*
От: Somescout  
Дата: 11.03.17 18:47
Оценка:
Здравствуйте, Pzz, Вы писали:

Pzz>2) Можно объявить szTmp, как static. Проблема в том, что следующий вызов функции подменит предыдущее содержимое этого буфера, и если кто-то где-то все еще использует старое значение, этот кто-то сильно удивится.


По-моему такое не стоит советовать даже в шутку. Имхо, но это эталонный выстрел в ногу.
ARI ARI ARI... Arrivederci!
Re[7]: return char*
От: Carc Россия http://www.amlpages.com/home.php
Дата: 11.03.17 19:42
Оценка:
Здравствуйте, Pzz, Вы писали:

Pzz>Здравствуйте, Carc, Вы писали:


Pzz>Сразу видно, что ты никогда не программировал в среде, которая использует твой текущий стек для обработки прерываний.


Pzz>Это совершенно не нарушает сишную calling convention, но данные, которые "над стеком" могут быть испорчены в любой момент.


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


Pzz>Не умничай, короче, целее будешь

Ты о себе?

Именно выше перечисленные варианты событий я и имел ввиду (служебные вызовы).
Цитата из моего первого поста
Автор: Carc
Дата: 11.03.17

Я где-то увидел такое решение, и чуть не припух. Ибо проект был солидный, и парни там грамотные. Очень долго въезжал, что к чему. Потом стало ясно.


Я про тот случай и говорил. Что очень удивился вообще такому решению. И что оно вообще может работать. Это только информация к размышлению топик-стартеру. И там же первом посте написал к чему приводит использование таких "оптимизаций"
Aml Pages Home
Re[4]: return char*
От: Somescout  
Дата: 11.03.17 20:15
Оценка:
Здравствуйте, Pzz, Вы писали:

Pzz>Совершенно не обязательно. Такое решение плохо, так сказать, масштабируется, но масштабирование не всегда бывает нужно. Так можно делать, если хорошо понимать, какие это накладывает ограничения, и как на эти ограничения случайно не наступить.


Именно для целей возврата изменяемого значения? Если речь о единожды вычисляемой константе — вопросов нет, а вот возвращать результат, который может быть изменён в процессе работы программы — закладывать мину в код.

ЗЫ. Хотя наверно для устройств с ограниченными ресурсами это нормальный вариант.
ARI ARI ARI... Arrivederci!
Отредактировано 11.03.2017 22:59 Somescout . Предыдущая версия .
Re[3]: return char*
От: Кодт Россия  
Дата: 13.03.17 10:43
Оценка:
Здравствуйте, 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); // которые сделаны специально совместимыми для использования в сишных функциях
}
Перекуём баги на фичи!
Re[4]: return char*
От: uzhas Ниоткуда  
Дата: 13.03.17 11:20
Оценка:
Здравствуйте, Кодт, Вы писали:

К>
К>// хорошо №2
К>CStringA Sum();  // у ATL/MFC есть свои классы строк
К>void Work() {
К>  CStringA s = Sum();
К>  . . .
К>  printf("%s", s); // которые сделаны специально совместимыми для использования в сишных функциях
К>}
К>


не так хорошо, как в №1 =\
http://stackoverflow.com/questions/14432539/c-passing-classes-to-vararg-function

несмотря на поддержку в конкретном компиляторе, возникает соблазн использовать такое повсюду (писать свои такие же классы, затем писать так в гцц и тд)
в целом, плохая практика имхо
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.