Крошечная программа сбоя - PullRequest
0 голосов
/ 01 августа 2010

Следующая программа компилируется с g ++, но затем аварийно завершает работу:

class someClass
{
public:
    int const mem;
    someClass(int arg):mem(arg){}
};

int main()
{
    int arg = 0;
    someClass ob(arg);

    float* sample;
    *sample = (float)1;

    return 0;
}

Следующая программа не аварийно завершает работу:

int main()
{

    float* sample;
    *sample = (float)1;

    return 0;
}

Ответы [ 4 ]

8 голосов
/ 01 августа 2010
float* sample;
*sample = (float)1;

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

float f;
float* sample = &f;
*sample = (float)1;

Ваша вторая программа по-прежнему не работает, даже если она не падает.Разыменование указателя, который не указывает на допустимый объект, приводит к неопределенному поведению.В результате может произойти сбой вашей программы, некоторые другие данные в памяти будут перезаписаны, ваше приложение будет отображаться для продолжения корректной работы или любой другой результат.Может показаться, что ваша программа работает нормально сегодня, но вылетает, когда вы запускаете ее завтра.

4 голосов
/ 02 августа 2010

Подумав немного, я могу с некоторой долей уверенности сказать, почему 2-й пример не рухнул.

Когда программа выполняется, crt (c runtime) помещает в стек 3 значения: количество аргументов (int), аргументы как char ** и строки среды также как char **, затем звонит main.

Теперь, когда вы пишете свою функцию main, насколько я знаю, она всегда читает первые 2 значения и передает их в аргументы функции, если они есть.Если вы включите третий аргумент, он также передаст третье значение, иначе он останется в стеке.Таким образом, стек в начале программы выглядит следующим образом:

+--------+
| # args |  
+--------+    
|  args  |  
+--------+  <------ stack pointer
|  envs  |  
+--------+  

В первом примере вы выделяете int и структуру в стеке, затем указатель, поэтому полный стек вПервый пример выглядит так:

+--------+
| # args |  
+--------+    
|  args  |  
+--------+  <------ stack pointer
|  arg   |  <------ your integer, initialized in code
+--------+  
|   ob   |  <------ your struct, initialized in code
+--------+  
| sample |  <------ your pointer, uninitalized = garbage
+--------+  

Так что sample - это чистый мусор, и попытка разыменования приводит к сбою вашей программы.

Теперь во втором примере стек выглядит так:

+--------+
| # args |  
+--------+    
|  args  |  
+--------+  <------ stack pointer
| sample |  <------ pointer, uninitalized (!)
+--------+  

Указатель по-прежнему не инициализирован, но значение, которое он перезаписал, равно envp, что является фактическим указателем на массив: char **.Когда вы разыменовываете его, вы возвращаете массив «указателей на символ», так что вы можете перезаписать его безопасно (при условии, что вы больше не пытаетесь рассматривать его как исходный указатель).Память распределена, и вы можете ее использовать.

Теперь это, конечно, сильно зависит от реализации, но, похоже, подходит вашему компилятору, верно?С помощью gdb вы можете проверить, действительно ли (char**)sample указывает на массив переменных среды, прежде чем перезаписать его.

Снимок экрана из MSVC ++ 10 в 32-разрядном режиме выпуска (режим отладки принудительно инициализирует все переменные, чтобы предотвратитьтакие вещи):

указатель в действии http://img651.imageshack.us/img651/5918/69916340.png

0 голосов
/ 01 августа 2010

Вы разыменовываете унифицированный указатель.

Самое интересное, почему второй пример не дает сбой, потому что у него та же проблема

Кстати: для меня (gcc 4.4, amd64) оба примера сбоя.

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

0 голосов
/ 01 августа 2010

Разыменование неинициализированного указателя: http://www.cprogramming.com/debugging/segfaults.html

...