0

Good day.

I build this simple code on Intel cpu working on Windows 7 with MS VS Express 2013:

#include <stdio.h>
int f(int a, int b, int c);

int f(int a, int b, int c){
    int d = a*b + c;
    return d;
};

int main()
{
    printf("%d\n", f(1, 2, 3));
    return 0;
};

When I disassemble binary I see just main() function that push 5 (the result of f()) and call printf():

; int __cdecl main(int argc, const char **argv, const char **envp)
_main proc near
push    5
push    offset Format   ; "%d\n"
call    ds:__imp__printf
add     esp, 8
xor     eax, eax
retn
_main endp

I'm expect to see something like:

 push 3
 push 2
 push 1
 call _f

or doing similar things with registers (for x64). But can't find f() definition. Is there any conventions or compiler optimizations that allow passing result without routine call? Or it is a debuggers (IDA, OllyDBG) calculating? It looks like the result of f() was calculated when project was built.

1
  • Yes, it is optimization during compile time, not debugging. If you want to see the function, you should compile with option /Od which means disabled optimization and may be found in project options (Configuration properties/C++/Optimization). If you would compile your program in VS in debug mode, start debugging and view disassembly window you'd see the call as you expected.
    – w s
    Commented Nov 17, 2015 at 10:33

1 Answer 1

1

Many compilers have magic incantations for explicitly suppressing the inlining of certain functions, like __declspec(noinline). An alternative is introducing something that the optimizer is not allowed to 'see through', like a (volatile) function pointer or an external DLL function. A vararg function can sometimes be used with similar effect (i.e. pretend to use the result of some computation in order to force the compiler to actually perform it).

The convention underlying all this is the 'as if' rule in the C/C++ standards. The compiler must ensure that the observable behaviour of the resulting binary follows the defined semantics; how these results are arrived at is completely up to the compiler.

In connection with a good optimising compiler this makes the source code of a program a logical specification (or contract) of what the programmer expects to happen, and the physical realisation is fully up to the compiler. This is a good thing, and long overdue.

Contrast this to glorified macro assemblers like Turbo Pascal and older Delphis, where you know exactly which machine code fragment/template will get emitted for which source code construct (barring a small handful of tricks from the Dragon Book, like dead store elimination).

Here are a few relevant verses from Scripture (ISO/IEC 14882:2003 alias C++03):

The semantic descriptions in this International Standard define a parameterized nondeterministic abstract machine. This International Standard places no requirement on the structure of conforming implementations. In particular, they need not copy or emulate the structure of the abstract machine. Rather, conforming implementations are required to emulate (only) the observable behavior of the abstract machine as explained below.

A conforming implementation executing a well-formed program shall produce the same observable behavior as one of the possible execution sequences of the corresponding instance of the abstract machine with the same program and the same input. However, if any such execution sequence contains an undefined operation, this International Standard places no requirement on the implementation executing that program with that input (not even with regard to operations preceding the first undefined operation).

(IOW, if you invoke undefined behaviour then all bets are off and the compiler may do whatever it pleases - including, but not limited to, crashing your computer or selling your wife on Ebay. Modern compilers tend to rely heavily on this clause.)

The observable behavior of the abstract machine is its sequence of reads and writes to volatile data and calls to library I/O functions.

The least requirements on a conforming implementation are:

— At sequence points, volatile objects are stable in the sense that previous evaluations are complete and subsequent evaluations have not yet occurred.

— At program termination, all data written into files shall be identical to one of the possible results that execution of the program according to the abstract semantics would have produced.

— The input and output dynamics of interactive devices shall take place in such a fashion that prompting messages actually appear prior to a program waiting for input. What constitutes an interactive device is implementation-defined.

1
  • Thank you for your detailed answer. It is very informative and helpful
    – Alex_H
    Commented Nov 17, 2015 at 11:15

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.