views:

160

answers:

4

The following program compiles with g++ but then crashes upon running:

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;
}

The following program does not crash:

int main()
{

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

    return 0;
}
+8  A: 
float* sample;
*sample = (float)1;

sample is never initialized to point to an object, so when you dereference it, your program crashes. You need to initialize it before you use it, for example:

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

Your second program is still wrong, even though it does not crash. Dereferencing a pointer that does not point to a valid object results in undefined behavior. The result could be your program crashing, some other data in memory getting overwritten, your application appearing to continue to run correctly, or any other result. Your program could appear to run fine today but crash when you run it tomorrow.

James McNellis
... and this is a good reason not to learn 100% "the hard way". You will be on a constant wild goose chase with stuff like this.
tenfour
It's not irrelevant when you're trying to figure out what's wrong. :) I blame the compiler.
It's not the compiler's fault you have no clue what you're doing. It's like blaming the hammer because you have the dexterity of a turnip and hit your hand.
Blindy
@drenami: " I blame the compiler." Blame yourself. You wouldn't learn how to fly helicopter the hard way and then blame helicopter for crashing, right?
SigTerm
A: 

De-referencing an uninitialized pointer: http://www.cprogramming.com/debugging/segfaults.html

Murali VP
A: 

You are dereferencing an unitialized pointer.

The real interesting thing is, why the second example does not crash for you, because it has the same problem

BTW: For me (gcc 4.4, amd64) both example crash.

If you are really interested in why the second example does not crash for you, compile it with debugging information and start it in a debugger.

IanH
Because it leads to undefined behavior, so you aren't guaranteed anything at all.
GMan
+4  A: 

After a bit of thinking, I can tell you with some degree of certainty why the 2nd example didn't crash.

When a program is executed, the crt (c runtime) pushes on the stack 3 values: the number of arguments (int), the arguments as a char ** and the environment strings also as a char **, then calls main.

Now when you write your main function, as far as I know it always reads the first 2 values and passes them to the arguments of the function, if there are any. If you include the 3rd argument, it passes the 3rd value too, otherwise it's left on the stack. So the stack at the start of the program looks like this:

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

In the first example, you allocate the int and the struct on the stack, then the pointer, so the full stack in the first example looks like:

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

So sample is pure garbage and attempting to dereference it crashes your program.

Now in the second example, the stack looks like this:

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

The pointer is still uninitalized, but the value it overwrote is envp, which is an actual pointer to an array: char **. When you dereference it, you get back an array of "pointers to char", so you can overwrite it safely (as long as you don't try to treat this as the original pointer anymore). The memory is allocated and you can use it.

Now this is of course heavily implementation specific, but it seems to fit for your compiler right? You can check with gdb that (char**)sample indeed points to an array of environment variables before you overwrite it.

Screenshot from MSVC++ 10, in 32-bit release mode (debug mode initializes all variables forcefully to prevent this kind of stuff):

the pointer in action

Blindy