1

I made a small program that looked like this:

void foo () {
  char *str = "+++"; // length of str = 3 bytes
  char buffer[1];

  strcpy (buffer, str);

  cout << buffer;
}

int main () {
  foo ();
}

I was expecting that a stack overflow exception would appear because the buffer had smaller size than the str but it printed out +++ successfully... Can someone please explain why would this happened ?
Thank you very much.

2
  • 3
    This one problem is called 'Buffer overflow', not 'Stack overflow'. Stack overflow is caused when you try to write beyond bounds of memory declared for stack (for example, by unterminated recursion). Commented Jan 3, 2013 at 10:59
  • 1
    @Yossarian Considering that the buffer in question has automatic storage and thus is on the stack, I'd say the OP didn't really get the terminology wrong. There is obviously a buffer overrun, but he didn't see any evidence (e.g. a crash) of a stack overflow as result. Commented Jan 3, 2013 at 11:07

6 Answers 6

13

Undefined Behavior(UB) happened and you were unlucky it did not crash.
Writing beyond the bounds of allocated memory is Undefined Behavior and UB does not warrant a crash. Anything might happen.
Undefined behavior means that the behavior cannot be defined.

Sign up to request clarification or add additional context in comments.

1 Comment

Example of fail: Ideone. Example of success: Ideone. Anyway, that's even depend on options passed to compiler.
2

You don't get a stack overflow because it's undefined behaviour, which means anything can happen.

Many compilers today have special flags that tell them to insert code to check some stack problems, but you often need to explicitly tell the compiler to enable that.

Comments

2

Undefined behavior...

In case you actually care about why there's a good chance of getting a "correct" result in this case: there are a couple of contributing factors. Variables with auto storage class (i.e., normal, local variables) will typically be allocated on the stack. In a typical case, all items on the stack will be a multiple of some specific size, most often int -- for example, on a typical 32-bit system, the smallest item you can allocate on the stack will be 32 bits. In other words, on your typical 32-bit system, room for four bytes (of four chars, if you prefer that term).

Now, as it happens, your source string contained only 3 characters, plus the NUL terminator, for a total of 4 characters. By pure bad chance, that just happened to be short enough to fit into the space the compiler was (sort of) forced to allocate for buffer, even though you told it to allocate less.

If, however, you'd copied a longer string to the target (possibly even just a single byte/char longer) chances of major problems would go up substantially (though in 64-bit software, you'd probably need longer still).

There is one other point to consider as well: depending on the system and the direction the stack grows, you might be able to write well the end of the space you allocated, and still have things appear to work. You've allocated buffer in main. The only other thing defined in main is str, but it's just a string literal -- so chances are that no space is actually allocated to store the address of the string literal. You end up with the string literal itself allocated statically (not on the stack) and its address substituted where you've used str. Therefore, if you write past the end of buffer, you may be just writing into whatever space is left at the top of the stack. In a typical case, the stack will be allocated one page at a time. On most systems, a page is 4K or 8K in size, so for a random amount of space used on the stack, you can expect an average of 2K or 4K free respectively.

In reality, since this is in main and nothing else has been called, you can expect the stack to be almost empty, so chances are that there's close to a full page of unused space at the top of the stack, so copying the string into the destination might appear to work until/unless the source string was quite long (e.g., several kilobytes).

As to why it will often fail much sooner than that though: in a typical case, the stack grows downward, but the addresses used by buffer[n] will grow upward. In a typical case, the next item on the stack "above" buffer will be the return address from main to the startup code that called main -- therefore, as soon as you write past the amount of space on the stack for buffer (which, as above, is likely to be larger than you specified) you'll end up overwriting the return address from main. In that case, the code inside main will often appear to work fine, but as soon as execution (tries to) return from main, it'll end up using that data you just wrote as the return address, at which point you're a lot more likely to see visible problems.

Comments

1

Outlining what happens:

Either you are lucky and it crashes at once. Or because it's undefined technically you could end up writing to a memory address used by something else. say that you had two buffers, one buffer[1] and one longbuffer[100] and assume that the memory address at buffer[2] could be the same as longbuffer[0] which would mean that long buffer now terminates at longbuffer[1] (because the null-termination).

char *s = "+++";
char longbuffer[100] = "lorem ipsum dolor sith ameth";
char buffer[1];

strcpy (buffer, str);

/*
buffer[0] = +
buffer[1] = +
buffer[2] = longbuffer[0] = +
buffer[3] = longbuffer[0] = \0 <- since assigning s will null terminate (i.e. add a \0)
*/

std::cout << longbuffer; // will output: +

Hope that helps in clarifying please note it's not very likely that these memory addresses will be the same in the random case, but it could happen, and it doesn't even need to be the same type, anything can be at buffer[2] and buffer[3] addresses before being overwritten by the assignment. Then the next time you try to use your (now destroyed) variable it might well crash, and thats when debugging become a bit tedious since the crash doesn't seem to have much to do with the real problem. (i.e. it crashes when you try to access a variable on your stack while the real problem is that you somewhere else in your code destroyed it).

Comments

0

There is no explicit bounds checking, or exception throwing on strcpy - it's a C function. If you want to use C functions in C++, you're going to have to take on the responsibility of checking for bounds etc. or switch to using std::string.

In this case it did work, but in a critical system, taking this approach might mean that your unit tests pass but in production, your code barfs - not a situation that you want.

3 Comments

No no...that is not luck that is plain bad luck!
@AlokSave Yes, in retrospect I agree - you'd want it to crash, wouldn't you?
Ideally, one would want the program to crash every time they make a subtle mistake or leave out a bug, that is so very convenient than getting bitten in the a$$ when your code runs in time critical production environments. If only wishes were horses...
0

Stack corruption is happening, its an undefined behaviour, luckily crash didnt occur. Do the below modifications in your program and run it will crash surely because of stack corruption.

void foo () {
  char *str = "+++"; // length of str = 3 bytes
  int a = 10;
  int *p = NULL;
  char buffer[1];
  int *q = NULL;
  int b = 20;

  p = &a;
  q = &b;

  cout << *p;
  cout << *q;

  //strcpy (buffer, str);

  //Now uncomment the strcpy it will surely crash in any one of the below cout statment.
  cout << *p;
  cout << *q;

  cout << buffer;
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.