Казалось бы очевидная ошибка и понятно, что так делать нельзя. Это просто эксперимент.
В каком случае в value будет не равняться 5 в приведенном ниже примере?
Функция возвращает адрес локальной переменной и это ошибка. Значение value скорее всего будет 5, но гарантии нет.
Если подумать то получается, что в value то всегда будет 5.
Даже если это многопоточное приложение и другой поток прервет выполнение основного потока то стековая память то у каждого потока все равно разная.
Что же должно случиться, чтобы в value было не 5?
Я сам прокручивал самые разные версии вплоть до влияния ОС, процессорной архитектуры, типа компилятора и ключей оптимизации кода. Никаких результатов стабильно держится value=5 при всех комбинациях.
gcc, Visual Studio, отладоная сборка неотладочная , оптимизация включена/выключена.
Хотя программа конечно слишком простая в примере.
int *f(void)
{int a=5;
return a;
}
int main()
{
int value;
int *m=f();
value=m;
}
Здравствуйте, salvequick, Вы писали:
S>Что же должно случиться, чтобы в value было не 5?
примерно вот что:
#include <iostream>
int* foo()
{
int a = 5;
return &a;
}
int* bar()
{
int a = 10;
return &a;
}
int main()
{
int value;
int* m = foo();
int* n = bar();
value = *m;
std::cout << value << std::endl;
return 0;
}
zaufi@gentop /work/tests $ g++ -o fail fail.cc
fail.cc: In function 'int* foo()':
fail.cc:5:9: warning: address of local variable 'a' returned [enabled by default]
fail.cc: In function 'int* bar()':
fail.cc:11:9: warning: address of local variable 'a' returned [enabled by default]
zaufi@gentop /work/tests $ ./fail
10
вызов foo оставляет в стэке "след"... любой следующий вызов в данном случае перезатрет его...
Здравствуйте, salvequick, Вы писали:
S>Что же должно случиться, чтобы в value было не 5?
Между вызовом функции f() и чтением m вызвать другую функцию, которая поменяет стек. А может не поменяет, или поменяет, если по сети приходит слишком длинный пакет (два раза в сутки), мало ли какая логика там.
PS полагаю, что в примере опечатка и имелось в виду return &a;
S>Я сам прокручивал самые разные версии вплоть до влияния ОС, процессорной архитектуры, типа компилятора и ключей оптимизации кода. Никаких результатов стабильно держится value=5 при всех комбинациях.
Исправив ошибки (в таком виде она просто не компилируется) и добавив всего одну строчку, можно увидеть чем чреват возврат адреса локальной переменной:
#include <iostream>
using namespace std;
int* f() {
int a = 5;
return &a;
}
int main() {
int* pv = f();
cout << "PV: " << *pv << endl;
}
1. разберитесь c & и * в этом примере
2. rtfm по стеку — http://en.wikipedia.org/wiki/Call_stack и т.п.
3. после вызова такой функции (с сохранением результата в указатель/сcылку), вызовете какую-нибудь другую функцию, а потом прорвете значение по сохранённому указателю/ссылке
Здравствуйте, Vamp, Вы писали:
V>Исправив ошибки (в таком виде она просто не компилируется) и добавив всего одну строчку, можно увидеть чем чреват возврат адреса локальной переменной:
Я рассматриваю чистый С.
Да возвращается там адрес, это отпечатка.
vsb>Между вызовом функции f() и чтением m вызвать другую функцию, которая поменяет стек. А может не поменяет, или поменяет, если по сети приходит слишком длинный пакет (два раза в сутки), мало ли какая логика там.
Условия уточняю:
между int *m=f();
и value=m;
никаких вызовов нет. ничего не вызывается ,никто не трогает стек.
Используется чистый С.
Что будет потом по этому адресу не важно. Важно только то что в строке value=m;
vsb>PS полагаю, что в примере опечатка и имелось в виду return &a;
да опечатка.
Здравствуйте, salvequick, Вы писали:
S>Даже если это многопоточное приложение и другой поток прервет выполнение основного потока то стековая память то у каждого потока все равно разная.
Ещё есть системные прерывания и сигналы.
Если пришел сигнал, то его обработчик может и затереть соответствующую память. Впрочем, если ОС гарантирует наличие Red Zone, то может и не затереть.
Если значение по возвращенному адресу вытащить сразу после вызова функции, как у вас, а не как тут предлагают с cout)) то есть без использования стека, то действительно очень сложно представить ситуацию, когда бы значение испортилось. Можно подумать в сторону того, что некоторые компиляторы могут вернуть значение в регистре(для АРМ вполне вероятно), а все разыменования заменить на простое присваивание из регистра выкинув указатель m, тогда достаточно будет:
int main()
{
int value;
int *m=f();
int n = 9; // n может храниться в том же регистре
value=*m;
}
Здравствуйте, watch-maker, Вы писали:
WM>Если пришел сигнал, то его обработчик может и затереть соответствующую память. Впрочем, если ОС гарантирует наличие Red Zone, то может и не затереть.
У такого обработчика дожен быть совершенно другой контекст по идее.
И следовательно другой стек.
Здравствуйте, salvequick, Вы писали:
S>Коллеги,
S>Казалось бы очевидная ошибка и понятно, что так делать нельзя. Это просто эксперимент.
S>В каком случае в value будет не равняться 5 в приведенном ниже примере? S>Функция возвращает адрес локальной переменной и это ошибка. Значение value скорее всего будет 5, но гарантии нет. S>Если подумать то получается, что в value то всегда будет 5. S>Даже если это многопоточное приложение и другой поток прервет выполнение основного потока то стековая память то у каждого потока все равно разная. S>Что же должно случиться, чтобы в value было не 5?
В отладочной конфигурации компилятор вполне может помечать освобожденную стековую память каким-нибудь определенным значением — для облегчения диагностики подобных ошибок.
--
Не можешь достичь желаемого — пожелай достигнутого.
> Я сам прокручивал самые разные версии вплоть до влияния ОС, процессорной > архитектуры, типа компилятора и ключей оптимизации кода. Никаких результатов > стабильно держится value=5 при всех комбинациях. > gcc, Visual Studio, отладоная сборка неотладочная , оптимизация включена/выключена.
Зачем об этом рассуждать, если так делать всё равно нельзя?
Вот гляди, вот человек допустим берёт в руки нож, и со всей дури
втыкает себе в живот. Что будет ? Я думаю, что во всех без исключения
случаях человеку будет плохо. Я даже представить себе не могу другой
исход. Ну разве что он промахнётся. Ну и что ? Нам полезно это знание?
Да нет, не будет он себя ножём в живот тыкать. Просто не будет.
Здравствуйте, salvequick, Вы писали:
S>У такого обработчика дожен быть совершенно другой контекст по идее.
По какой ещё идее? Обработчик же может принадлежать программе.
S>И следовательно другой стек.
Создавать новый полноценный стек на каждый приход сигнала. Шутишь?
Когда происходит прерывание или приходит сигнал, то этим обычно занимается ядро, у него конечно же есть свой стек. Но сама-то обработка происходит уже в процессе.
И разумеется фрейм под обработчик выделяется в стеке процесса, вот пример — http://ideone.com/9RTQo — никакого нового стека.
Всё что находится ниже указателя указателя на вершину стека при этом затирается, включая и память под локальные переменных уже завершенных функций. Не затирается только Red Zone, но её наличие и размеры зависят от ОС и архитектуры процессора (например, в Windows её нет, во многих x86 — тоже, а вот в *nix с amd64 она есть).
Да, тут ещё можно вспомнить всякие исторические штуки, вроде обработки прерываний в DOS, где стек процесса использовался как минимум для сохранения регистров IP и FLAGS. Но, наверно, DOS уже слишком устаревший пример.
MZ>Зачем об этом рассуждать, если так делать всё равно нельзя?
Не согласен, кстати. По двум причинам:
1. Человек разумный хочет понимать, почему нельзя. В чем причина запрета? В каких случаях все-таки можно? Классический пример — старые контейнеры в стандарте 98. Как известно, в них запрещено класть авто_птр. А почему? Потому, что сортировка нормально работать не будет. Если не понимать, почему нельзя, то придется обходиться без автопойнтеров в контейнере — а они бывают очень полезные. А если понимать, то можно класть, но не сортировать стандартной сортировкой.
2. Надо знать, к каким последствиям приводит нарушение запрета, чтобы по симптомам уметь вычислять причину и исправлять ее.
MZ>Вот гляди, вот человек допустим берёт в руки нож, и со всей дури MZ>втыкает себе в живот. Что будет ? Я думаю, что во всех без исключения MZ>случаях человеку будет плохо. Я даже представить себе не могу другой MZ>исход.
Никогда не видал йогинов?
On 04/26/2012 12:50 AM, Vamp wrote:
> 1. Человек разумный хочет понимать, почему нельзя. В чем причина запрета? В
Больно будет как минимум! Что тут не понятного ?
> каких случаях все-таки можно? Классический пример — старые контейнеры в > стандарте 98. Как известно, в них запрещено класть авто_птр. А почему? Потому, > что сортировка нормально работать не будет.
Нет, не поэтому. а ПО СТАНДАРТУ!
Если не понимать, почему нельзя, то > придется обходиться без автопойнтеров в контейнере — а они бывают очень > полезные. А если понимать, то можно класть, но не сортировать стандартной > сортировкой.
Нельзя даже класть.
> 2. Надо знать, к каким последствиям приводит нарушение запрета, чтобы по > симптомам уметь вычислять причину и исправлять ее.
Здравствуйте, MasterZiv, Вы писали:
>> 2. Надо знать, к каким последствиям приводит нарушение запрета, чтобы по >> симптомам уметь вычислять причину и исправлять ее.
MZ>Это UB, симптомы могут быт ЛЮБЫЕ.
Это в теории могут. А в конкретных реализациях не такие уж и любые
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, salvequick, Вы писали:
S>Я рассматриваю чистый С.
Это не важно.
Оптимизирующий компилятор С тоже умеет переставлять местами функции.
Давай немного изменим твой пример?
int* makeUB( int i ) { return &i; }
int main()
{
int* p1 = makeUB( 5 );
int result = *p1;
int* p2 = makeUB( 10 );
printf( "*p2 = %d; *p1 = %d\n", *p2, result );
return result;
}
Оптимизирующий компилятор, по идее, может переставить местами
int result = *p1;
int* p2 = makeUB( 10 );
в зависимости от не пойми чего.
Кроме того, бывают однопоточные платформы, на которых можно словить прерывание...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
MZ>Зачем об этом рассуждать, если так делать всё равно нельзя? MZ>Вот гляди, вот человек допустим берёт в руки нож, и со всей дури MZ>втыкает себе в живот. Что будет ?
От человека зависит. Например, история советской медицины знает случай удаления апендицита самому себе...
Ясен пень, что от хорошей жизни так не делают, но кто обещал, что жизнь всегда будет хорошей?
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
>> Я сам прокручивал самые разные версии вплоть до влияния ОС, процессорной >> архитектуры, типа компилятора и ключей оптимизации кода. Никаких результатов >> стабильно держится value=5 при всех комбинациях. >> gcc, Visual Studio, отладоная сборка неотладочная , оптимизация включена/выключена.
MZ>Зачем об этом рассуждать, если так делать всё равно нельзя?
Это еще почему?
MZ>Вот гляди, вот человек допустим берёт в руки нож, и со всей дури MZ>втыкает себе в живот. Что будет ? Я думаю, что во всех без исключения
Эта конкретная процедура называется сепукка. Вполне себе легитимная операция в некоторых культурах.
MZ>случаях человеку будет плохо. Я даже представить себе не могу другой MZ>исход. Ну разве что он промахнётся. Ну и что ? Нам полезно это знание?
Знание полезно. Хотя бы за для понять как устроен стек.
Взятия адреса внутренней переменной функции используется например в консервативных GC для определения
текущей глубины стека. Да мало ли...
S>Казалось бы очевидная ошибка и понятно, что так делать нельзя. Это просто эксперимент. S>В каком случае в value будет не равняться 5 в приведенном ниже примере?
Нигде не регламентируется, что для хранения автоматических переменных используется стандартный стек. Могут использоваться регистры.
В данном случае оптимизирующий компилятор имеет полное право вообще вернуть мусор и будет прав.