1

I implemented simple versions of the factorial function which both can be seen here. I'm using GCC Trunk. There are 2 source files, the first being the function version and the second being the templated version. Each source file has 2 attached compilers with an associated output. The only difference between the two attached compilers for each source file is that the lefthand compiler has no compiler flags or optimizations turned on, and the righthand version has -O3 set to turn on level 3 optimizations.

Here are my functions:

// Function Version
int factorial(int n) {
    if (n == 0 || n == 1) return 1;
    if (n == 2) return 2;
    return (n * factorial(n-1));
}

// Template Version:
template<unsigned N>
static auto constexpr factorial_t() {
    return (N * factorial<N-1>());
}

template<>
auto constexpr factorial_t<0>() {
    return 1;
}
template<>
auto constexpr factorial_t<1>() {
    return 1;
}
template<>
auto constexpr factorial_t<2>() {
    return 2;
}

Now, when I run these in my installed IDE Visual Studio 2017 on my local PC using c++17, I am getting the expected output returned back from main(), and the returned value is correct for both implementations.

I ported this to Compiler Explorer to test other compilers and their optimizations to compare their generated assembly instructions. This is a fairly straightforward process.

Now when I run these functions as such:

source#1

int main() {
    return factorial(6);
}

source#2

int main() {
    return factorial_t<6>();
}

Compiler Explorer generates the following instruction counts...

  •          |   Assembly Instruction Count   |
    
  • Type     | Without O3 | With O3 Turned On |  
    
  • Function |     34     |       29          |
    
  • Template |     50     |        3          |
    

All is good.

All four program executions return a value of 208.

Now to my question:

However, it's not obvious within the assembly of the first and third compilers directly without doing some register math, but in the second and last with -O3 turned on for both the function and template versions, the value 720 is being stored into the EAX register before main()'s return call. Why is Compiler Explorer displaying: Program returned: 208 instead of 720?

Francis Cugler
  • 7,788
  • 2
  • 28
  • 59
  • 2
    `main` can only return an 8 bit value - 720 % 256 = 208 – Paul R Feb 12 '21 at 08:10
  • @PaulR Oh okay... Yeah, when I was looking at it, I was thinking maybe hex, but 720 in hex is not 208... it didn't dawn on me that `main` was only returning a single byte... yet the `function` declaration of `main()`'s return type is `int` which should be, 16, 32 or 64 bit depending on both architecture and OS... I always assumed `main()`'s return type was an `int`. It never dawned on me to do `modulus` arithmetic... – Francis Cugler Feb 12 '21 at 08:13
  • Yes, it’s historical, since main only returns a status code I guess people felt that 8 bits were sufficient. – Paul R Feb 12 '21 at 08:14
  • @PaulR However, if that's the case, then why does it display `720` to the command prompt in Windows 7 on my 64bit machine when I run either version that the program had returned? – Francis Cugler Feb 12 '21 at 08:15
  • Windows always has to be different. ;-) – Paul R Feb 12 '21 at 08:16
  • 1
    @PaulR Don't get me wrong, I appreciate your feedback on narrowing down this discrepancy... I was just shaking my head there for a while trying to figure out `how`, `where`, and `why` this value was being generated... – Francis Cugler Feb 12 '21 at 08:16
  • @PaulR So I'm guessing because `C++` standard allows specific things within the language to be `implementation-defined` from one compiler to another and from one OS to another... this may then come down to `calling conventions` and how each compiler treats `main()` with respect to both the architecture and OS it is used on... – Francis Cugler Feb 12 '21 at 08:19
  • @PaulR I think this might lead to another `Q/A`... – Francis Cugler Feb 12 '21 at 08:23
  • 2
    `main` does return `int`, but the C++ standard doesn't say how many bits of it will be used by OS. Now, POSIX does use only 8 bits (Windows uses 32 bits), see also https://stackoverflow.com/questions/5149228/return-value-range-of-the-main-function – heap underrun Feb 12 '21 at 08:25
  • See also: https://stackoverflow.com/q/8082953/253056 – Paul R Feb 12 '21 at 08:29
  • @heapunderrun When I switched Compiler Explorer to use `MSVC x64 latest`... with -O2 set... It was returning 0... so this was giving me the assumption that `720 mod` `2^32` will equal `0` as there is no remainder... – Francis Cugler Feb 12 '21 at 08:30

1 Answers1

3

main is defined to return an int. After main returns the c++ runtime will call std::exit (or equivalent code) with the value returned from main.

std::exit also takes an int exit code. How this exit code is turned into a process return code is implementation defined.

On Unix the return code for a process is usually a single unsigned byte so the int is simply truncated to fit into 0 - 255.

On Windows process return codes are 32-bit signed integers so the int value passed to std::exit is returned directly.

Alan Birtles
  • 32,622
  • 4
  • 31
  • 60
  • Not realizing that `main()` only returns `8-bits` on `GCC` or `Clang` as opposed to `MSVC` for `Windows`, I guess this is coming from the lack of using `Linux` or other `POSIX` `Unix` - `BSD` derived Operating Systems... 95% of the time I'm using Windows specifically for my desktop. The only variants of POSIX I may use would be from devices such as some phones `Android` and TV Device boxes such as `Roku`, `Kodi` like devices, and Amazon's `FireTV` sticks or boxes, and Smart TVs... However, I don't do much programming on them, I just use their interfaces to use them... – Francis Cugler Feb 12 '21 at 08:48
  • 1
    @FrancisCugler `main` always returns an `int`, how that is translated into a process return code is platform specific (not compiler specific, e.g. if you use clang or gcc on windows you'll probably get 32-bit exit codes) – Alan Birtles Feb 12 '21 at 09:13
  • True... that makes complete sense since this would come down to the operating system's syscalls... – Francis Cugler Feb 12 '21 at 09:22