2

I know it must've been answered somewhere, but I didn't find any information regarding this strange behavior. I was just messing around with the heap, and when I executed the program with zero optimiziations, there was no error. I went to godbolt and it looks like there's no assembly instructions at all.

  • What happens to the code when you pass only -O without any level?
  • What's the difference between this and -O0, that by the way, works well?
int main(int argc, char **argv) 
{
    char* x = malloc(10);
    char n = x[11];
}
$ gcc -O -g -fsanitize=address main.c -o main
$ ./main # No problems
gdb) info locals # Without -fsanitize=address
x = <optimized out>
n = <optimized out>
Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
feegLamar
  • 29
  • 3
  • https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html `-O` and `-O1` are the same thing. – Retired Ninja Jun 27 '22 at 14:20
  • @RetiredNinja Oh. Anyway why would these local vars be optimized out? – feegLamar Jun 27 '22 at 14:22
  • If you use `-fasnitize=address,undefined` there is an error for the out of bounds access. https://godbolt.org/z/7n18fz7b7 With optimization on the compiler will usually attempt to remove code that it can determine has no side effects. If you were to use `n` in some way then the code would need to be generated but since it isn't used it can be eliminated. – Retired Ninja Jun 27 '22 at 14:33
  • @feegLamar: Because they have no ultimate effect, so they do not cause any observable behavior, so the compiler optimized them out. – Eric Postpischil Jun 27 '22 at 14:33
  • @RetiredNinja - _If you were to use n in some way then the code would need to be generated but since it isn't used it can be eliminated_, strange, because whenever I declare local integer variables, they never get optimizied out, and I don't use them. Can you explain? – feegLamar Jun 27 '22 at 14:38
  • 1
    @feegLamar, Not without the code and the build command you used. But I'm guessing you didn't enable optimizations that time. – ikegami Jun 27 '22 at 14:53
  • 1
    @ikegami - Try it yourself, [godbolt](https://godbolt.org/z/6qGo9fzWa), ```(gdb) info locals q = 5, l = 0, x = , n = ``` – feegLamar Jun 27 '22 at 15:03
  • Re "*Try it yourself, godbolt*", huh, you just showed that they were optimized out. The program initializes the address sanitizer and exits. That's all. – ikegami Jun 27 '22 at 15:15
  • @ikegami lol but gdb still can tell their values and I'm asking why – feegLamar Jun 27 '22 at 15:17
  • No, `q = 5, l = 0` were somehow not optimized out at least in GDB, I can confirm that. – ks1322 Jun 27 '22 at 15:18
  • No, you asked why they weren't getting optimized out. In any case, the comments it not the place for new questions. When I said you needed to provide the code and build command you used (which you still haven't done), I didn't mean in the comments. – ikegami Jun 27 '22 at 15:19
  • @ikegami This time I asked why they weren't getting optimized out of GDB. You can see the code + build commands very clearly in godbolt – feegLamar Jun 27 '22 at 15:19
  • And they are optimized out in godbolt – ikegami Jun 27 '22 at 15:20
  • @ikegami GDB though – feegLamar Jun 27 '22 at 15:21
  • We've already covered this... – ikegami Jun 27 '22 at 15:21
  • @ikegami Where? I don't see any answer talking about why `GDB` can still tell the values of optimized variables – feegLamar Jun 27 '22 at 15:22
  • 1
    "In any case, the comments it not the place for new questions. When I said you needed to provide the code and build command you used (which you still haven't done), I didn't mean in the comments. " – ikegami Jun 27 '22 at 15:22
  • @ikegami It might have better from you to provide a simple + one comment answer then, if you can ofc. – feegLamar Jun 27 '22 at 15:23
  • @ikegami The build and the code is the same as the ones in godbolt. – feegLamar Jun 27 '22 at 15:26
  • Re: compilers being able to optimize away malloc/free pairs, or memory leak mallocs: [Does compiler optimization affect on dynamic memory allocation?](https://stackoverflow.com/q/54056344) / [Is the compiler allowed to optimize out heap memory allocations?](https://stackoverflow.com/q/31873616) – Peter Cordes Jun 28 '22 at 02:56

3 Answers3

2

I went to godbolt and it looks like there's no assembly instructions at all

Indeed, GCC optimized out all your code because you do not use in any way. If you change it slightly then it will stay in generated assembly and AddressSanitizer will find heap overflow.

The following code generates AddressSanitizer heap-buffer-overflow error as you expect:

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    char* x = malloc(10);
    char n = x[11];

    return n;
}
ks1322
  • 33,961
  • 14
  • 109
  • 164
  • GCC is a really smart compiler! Thank you. But, can you explain why normal local integers don't get optimizied out, even when I don't use them at all? I double-checked it with GDB. – feegLamar Jun 27 '22 at 14:42
  • It is unclear what is "normal local integers". Could you ask another question and post the code reproducing this behavior? Also no need to check it in GDB, it's enough to see generated code on godbolt. – ks1322 Jun 27 '22 at 14:50
  • I declared a couple of signed integers `int z = 0; int b = 5;` and when I view the local main variables, they don't seem to get optimizied out, only `char *x` and `char n` get cleared out. – feegLamar Jun 27 '22 at 14:51
  • This https://godbolt.org/z/4ex9z3WrY generates the same optimized assembly as your original code. – ks1322 Jun 27 '22 at 15:03
  • yeah but gdb doesn't tell me they were optimized out, instead he can actually see their values when I `info locals` – feegLamar Jun 27 '22 at 15:08
  • I see, but I have no good answer about GDB right now. – ks1322 Jun 27 '22 at 15:14
2

What happens to the code when you pass only -O without any level?

It's the same as -O1, "optimize a little bit"

What's the difference between this and -O0, that by the way, works well?

Since your code has no obvious side effects, it will be removed by the optimizer if enabled. This happens no matter if you invoke undefined behavior with x[11] or if you access a valid index x[0]. Writing to the x[0] item first to ensure it isn't indeterminate doesn't have any effect either.

-O0 explicitly disables optimizations, so you'll get some manner of machine code generated... maybe. The compiler doesn't have to generate anything predictable or meaningful in case your code contains undefined behavior.

There's generally no bounds-checking in C, it's the programmer's responsibility to handle it.


As for -fsanitize=address, add a side effect and it might kick in:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv) 
{
    char* x = malloc(10);
    char n = x[11];
    printf("%d\n", n);
}

Results in:

==1==ERROR: AddressSanitizer: heap-buffer-overflow on address-...

Lundin
  • 195,001
  • 40
  • 254
  • 396
2

The docs answer both of your questions.

What happens to the code when you pass only -O without any level?

-O
-O1

Optimize. Optimizing compilation takes somewhat more time, and a lot more memory for a large function.

[...]

It enables optimizations.

What's the difference between this and -O0

-O0

Reduce compilation time and make debugging produce the expected results. This is the default.

As the default, it really does nothing at all.

In this mode, optimizations (or at least non-trivial ones) are effectively disabled.


Zero optimiziation doesn't detect heap overflow

On Compiler Explorer, you did enable optimizations (by using -O).

If you stop optimizing the variables away (by removing -O or by using -O0), you get the expected error.

<source>: In function 'main':
<source>:7:15: warning: unused variable 'n' [-Wunused-variable]
    7 |          char n = x[11];
      |               ^
<source>:7:15: warning: 'x[11]' is used uninitialized [-Wuninitialized]
    7 |          char n = x[11];
      |               ^

Program returned: 1
Program stderr

=================================================================
==1==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x60200000001b at pc 0x0000004011a4 bp 0x7fffc6caef80 sp 0x7fffc6caef78
READ of size 1 at 0x60200000001b thread T0
    #0 0x4011a3 in main /app/example.c:7
    #1 0x7f88c4c140b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x240b2)
    #2 0x40109d in _start (/app/output.s+0x40109d)

0x60200000001b is located 1 bytes to the right of 10-byte region [0x602000000010,0x60200000001a)
allocated by thread T0 here:
    #0 0x7f88c4e9d1af in malloc (/opt/compiler-explorer/gcc-12.1.0/lib64/libasan.so.8+0xbb1af)
    #1 0x401167 in main /app/example.c:6
    #2 0x7f88c4c140b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x240b2)

[snip]

Demo on Compiler Explorer

The same would happen with optimizations if the program actually used n.

ikegami
  • 367,544
  • 15
  • 269
  • 518