Hacker News new | past | comments | ask | show | jobs | submit login

Why would the compiler be allowed optimize away a call to a perfectly valid function? This seems like it's allowing to compiler to make judgement calls on whether or not your code is worthy of being executed.



It's because memset is a standard function and has a defined standard way of acting, with the most important part being that it doesn't produce any side-effects. It's also worth noting that accessing memory that's no longer in the current scope is undefined-behavior, so the compiler can assume it doesn't happen. Thus the memset has absolutely zero effect on the actual program and isn't necessary.

In general this isn't a big deal. It's only a big deal here because we're working on the assumption that you might have an issue in your code that invokes undefined-behavior and access contents of memory that you're not supposed to be looking at anymore, and the compiler's just assuming that your program won't ever allow that.


What if one made some trivial use of the block of memory after having performed the memset, say something like this:

   void
   dosomethingsensitive(void)
   {
        uint8_t key[32];
        ...
        /* Zero sensitive information. */
        memset((volatile void *)key, 0, sizeof(key));
        key[0] = key[1] + 1;
   }
Would that thwart the optimizer, or would it also see through that usage and eliminate it as well?


You should keep in mind that the memset call is almost guarenteed to be inlined. So your code actually looks like this:

    void
    dosomethingsensitive(void)
    {
        uint8_t key[32];
        /* ... */
        int i;
        for (i = 0; i < 32; i++)
            key[i] = 0;
        key[0] = key[1] + 1;
    }
Assuming the optimizer is sufficiently smart, then it'll remove that 'for' and it'll remove the addition after it in the same fashion.


That just creates another dead store which will get eliminated.


Because the C standard permits such optimizations. It is really just a case of inlining followed by dead store optimization, two fairly common optimization passes which are normally desired.

The subtlety is that while the compiler considers it a dead store, you don't, because you're going behind the compilers back to examine memory afterwards.


How about compilers having a way for us to tell it that a particular function must not be removed? It seems silly that we have to come up with hacks to work around the compiler.


They do. Every function they don't know about is considered important, potentially having side effects and the call will remain. Just call a function that exists in a library somewhere else (but use a static library and link-time optimization and that goes wrong again, because the optimizer knows the function then).


Or, for that matter, use a dynamic library and it can still go wrong! (hypothetical future JITter or somesuch)


The compiler constantly makes judgement calls on whether or not your code needs to run.

For instance:

    if (something)
        do_something();
Elsewhere:

    #ifdef CONFIG_RUNTIME_ENABLE_SOMETHING
    bool something = false;
    /* and some means of changing something at runtime */
    #else
    const bool something = false;
    #endif
If you don't define CONFIG_RUNTIME_ENABLE_SOMETHING, and thus "something" cannot change at runtime, then the compiler should recognize the if as always false and throw away the call to do_something(). If that was the only call to do_something(), it should throw away the code of do_something().


There's a lot of dead code in real programs, and this kind of optimization gets rid of a lot of it.


...especially for code that's macro heavy. It's easy to have code where a macro is including a memset to protect some invariant. The compiler can see the bigger picture and realizes that the memset target isn't actually read from again in this case.


Why would it not. That is the whole point of optimizing compilers. You write the code with as many variables and functions calls as it makes sense and the compiler figures out all the redundant stuff and throws it away, inlines some other things, packs structs, etc.


The "result" of compiling a C program is defined in terms of observable behavior. It's mostly stuff like IO and writes to `volatile` variables and that sort of thing. There's no presumption of any sort of correspondence between the source code and the generated code and really a compiler can emit whatever, except that the observable behavior must be the same as that of a translation that actually follows the language definition.

Since there's no accounting for shenanigans like using a debugger to look at variables that are never accessed anymore or dumping the entire stack to a file at arbitrary points etc, the compiler is free to consider code unworthy of being executed if it provably doesn't contribute to further observable behavior of your program.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: