Different outputs on different compilers

Asked

Viewed 237 times

2

I have this code:

#include <iostream>
int main(int x=1) {
  while (x <= 1000 && std::cout << x++ << std::endl) {}
 }

I wrote it on Gedit, using Debian, and compiled it with g++ from Debian itself, without ever having changed anything. The compiler never presented problems in other codes. However, in my code the output I receive is the range from 2 to 1000.

And if I compile using this site https://ideone.com/4663hX, for example, the output is from 1 to 1000.

Something interesting is that: if I change the type of validation within the while, less than or equal to 1000 for less than 1000, only the program now displays the number 1.

What is going on?

NOTE: The code was developed for a challenge. Therefore, there is no discussion of a better way to develop the same output.

Assembly code:

.file   "tes.cpp"
    .local  _ZStL8__ioinit
    .comm   _ZStL8__ioinit,1,1
    .text
    .globl  main
    .type   main, @function
main:
.LFB969:
    .cfi_startproc
    pushl   %ebp
    .cfi_def_cfa_offset 8
    .cfi_offset 5, -8
    movl    %esp, %ebp
    .cfi_def_cfa_register 5
    andl    $-16, %esp
    subl    $16, %esp
.L4:
    cmpl    $1000, 8(%ebp)
    jg  .L2
    movl    8(%ebp), %eax
    addl    $1, 8(%ebp)
    movl    %eax, 4(%esp)
    movl    $_ZSt4cout, (%esp)
    call    _ZNSolsEi
    movl    $_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, 4(%esp)
    movl    %eax, (%esp)
    call    _ZNSolsEPFRSoS_E
    movl    (%eax), %edx
    subl    $12, %edx
    movl    (%edx), %edx
    addl    %edx, %eax
    movl    %eax, (%esp)
    call    _ZNKSt9basic_iosIcSt11char_traitsIcEEcvPvEv
    testl   %eax, %eax
    je  .L2
    movl    $1, %eax
    jmp .L3
.L2:
    movl    $0, %eax
.L3:
    testb   %al, %al
    jne .L4
    movl    $0, %eax
    leave
    .cfi_restore 5
    .cfi_def_cfa 4, 4
    ret
    .cfi_endproc
.LFE969:
    .size   main, .-main
    .type   _Z41__static_initialization_and_destruction_0ii, @function
_Z41__static_initialization_and_destruction_0ii:
.LFB978:
    .cfi_startproc
    pushl   %ebp
    .cfi_def_cfa_offset 8
    .cfi_offset 5, -8
    movl    %esp, %ebp
    .cfi_def_cfa_register 5
    subl    $24, %esp
    cmpl    $1, 8(%ebp)
    jne .L6
    cmpl    $65535, 12(%ebp)
    jne .L6
    movl    $_ZStL8__ioinit, (%esp)
    call    _ZNSt8ios_base4InitC1Ev
    movl    $__dso_handle, 8(%esp)
    movl    $_ZStL8__ioinit, 4(%esp)
    movl    $_ZNSt8ios_base4InitD1Ev, (%esp)
    call    __cxa_atexit
.L6:
    leave
    .cfi_restore 5
    .cfi_def_cfa 4, 4
    ret
    .cfi_endproc
.LFE978:
    .size   _Z41__static_initialization_and_destruction_0ii, .-_Z41__static_initialization_and_destruction_0ii
    .type   _GLOBAL__sub_I_main, @function
_GLOBAL__sub_I_main:
.LFB979:
    .cfi_startproc
    pushl   %ebp
    .cfi_def_cfa_offset 8
    .cfi_offset 5, -8
    movl    %esp, %ebp
    .cfi_def_cfa_register 5
    subl    $24, %esp
    movl    $65535, 4(%esp)
    movl    $1, (%esp)
    call    _Z41__static_initialization_and_destruction_0ii
    leave
    .cfi_restore 5
    .cfi_def_cfa 4, 4
    ret
    .cfi_endproc
.LFE979:
    .size   _GLOBAL__sub_I_main, .-_GLOBAL__sub_I_main
    .section    .init_array,"aw"
    .align 4
    .long   _GLOBAL__sub_I_main
    .hidden __dso_handle
    .ident  "GCC: (Debian 4.7.2-5) 4.7.2"
    .section    .note.GNU-stack,"",@progbits
  • 1

    You can show the output for this: int main(int x, char** v) { std::cout << x << std::endl; }? Must be 1.

  • It’s a real one. And I already knew that. Something interesting is that: if I change the type of validation inside the while, from smaller or equal to smaller, only, the program starts to display the number 1.

  • What version of GCC?

  • It is version 4.7.

  • I’m running out of possibilities... Compile your code for Assembly and add the result to the question. g++ file.cpp -S -o file.s

  • I want the Assembly code your compiler generates.

  • I edited the question by adding the generated code.

  • Um... my impression is that you are passing an argument on the command line when the value starts with 2. Can you check this? If so, maybe your question has a very local context and should be closed. But, I’m not sure. Maybe it’s a good motivation to include knowledge about command line arguments (and so I posted a response myself).

Show 3 more comments

3 answers

3

The problem is that the main function should only take 0 or 2 arguments (int argc, char** argv) according to the C++ standard. Since your code does not comply with C++ rules, each compiler can treat it differently. The G++, for example, generates a Warning with your prototype (use -Wmain if you’re not showing it to yourself), but seems to treat it as if it were the argc.

As a general rule, make sure your code is C++ compliant and you’ll have fewer problems switching compilers/platforms.

The shapes defined by the pattern are as follows:: int main() {

or

int main(int argc, char** argv) {

  • I did not find it necessary to vote against her reply, but she does not seem very precise because the behavior is not necessarily indefinite.

  • The behavior may not be undefined by the fact that a given compiler accepts this form. However, the C++ standard defines only the forms without arguments and the form with the argc and argv arguments (section 3.6.1), so that each compiler can do what he wants in other cases. I will edit the answer to make that point clearer.

  • He greatly improved his response (mainly with the Warning indication in G++). : ) Anyway, I still find it a little incorrect to say that the OP code "is not in accordance with the rules of C++", since the standard (I consulted the latest draft version) only requires the existence of these two most common prototypes (and does not prevent others, as you yourself mention). Ah, it would be nice to also include in your reply the link to the pattern you used as reference.

  • P.S.: I also found the advice to use one of these two prototypes to have less problems when changing compiler/platform.

  • Yes, I received the warning on the console. It might even be this... But I don’t know for sure. So I asked.

3


Your code uses a main function with the signature int main(int), that is legal, as other responses have already cited. However the GCC accepts such signature generating only a Warning, not an error. The code is compiled smoothly and does what is expected. The reason for the behavior is not there. (although it is something that should be fixed anyway).

Passing a default value for this argument does not make the slightest sense since the system will always call the function main explicitly passing the value. In this case it will be 1 if you call the program with no parameter on the command line, 2 if using a parameter, 3 for two parameters and so on. Note that in assmbly is clear and without shadow none of doubt that the first value displayed on the screen is the argument passed to the function main.

main:                              // Início da função 'main'
.LFB969:                           // Prólogo: Alocar 16 bytes de stack
    .cfi_startproc
    pushl   %ebp
    .cfi_def_cfa_offset 8
    .cfi_offset 5, -8
    movl    %esp, %ebp
    .cfi_def_cfa_register 5
    andl    $-16, %esp
    subl    $16, %esp              // Fim do prólogo.
.L4:
    cmpl    $1000, 8(%ebp)         // Comparar o argumento com 1000
    jg  .L2                        // Se for maior, pule para L2. Não é.
    movl    8(%ebp), %eax          // Copie o argumento para EAX
    addl    $1, 8(%ebp)            // Incremente o argumento
    movl    %eax, 4(%esp)          // Prepare para passar EAX como argumento
    movl    $_ZSt4cout, (%esp)     // Prepare para passar std::cout como argumento
    call    _ZNSolsEi              // Chame 'std::cout << EAX';

Since there is no question that the first value that comes out on the screen is the function argument main. If you see 2 is why you passed a parameter on the command line when you invoked your executable. There is no other possibility.

2

When building your program in C++ you have some options for declaring the call input function main. The two most common are:

1 - Do not worry about receiving and handling binary/executable command line arguments. For example:

#include <iostream>
int main() {
    std::cout << "Ola mundo!" << std::endl;
    return 0;
}

2 - Receiving and handling binary/executable command line arguments. For example:

#include <iostream>
int main(int argc, char** argv) {
    std::cout << "Ola mundo!" << std::endl;
    std::cout << "Total de argumentos: " << argc << std::endl;
    for(int i = 0; i < argc; i++)
        std::cout << "Argumento #" << i << " = [" << argv[i] << "]" << std::endl;
    return 0;
}

However, the fact is that the definition of the prototype of this function in many compilers allows some variations. In Visual Studio 2013, for example, the "signature" of the prototype is described like this in the documentation:

int main( int argc[ , char *argv[ ] [, char *envp[ ] ] ] );

The square brackets (characters [ and ]) serve in that context only to indicate the optionality function parameters (i.e., bracketed parameters are optional).

Note: you can notice that it is also possible to receive and treat environment variables with the parameter envp.

Another detail is that the C++ language allows you to indicate, when defining your own functions, the parameters that are optional. This is done by simply defining a default value (default) which shall be used if the parameter is not used/reported in the call.

So, when building your code the way you did, you simply used only the first parameter (argc, which contains the number of binary/executable command line arguments, although you renamed it to x). As the second (and third) parameter of main is optional, it will (most likely - depend on the compiler implementation) receive the value NULL.

Also, you simply reset the default (default) value of this argument to 1. In practice, simply having it there always gets you at least the path and name of your own binary/executable (according to the documentation, this is always the value of argv[0], regardless of whether you provide additional arguments on the command line), so this default value is simply useless.

This all that I argued does not answer the fact (alleged by you, but still without proof: I simply could not reproduce in my local tests with VS 2013) the result change depending on the use of minor (<) or less than or equal (<=). Still, knowing the documentation and expected behavior of the function main, i tend to believe that the variations of the value of your variable x amid 1 and 2 result of you being passing some argument on the command line (even without realizing).

Anyway, if your interest is to receive via command line the number that should be used to start your counting, you can do as follows:

#include <iostream>
int main(int argc, char** argv)
{
    // Validação dos argumentos da linha de comando
    if(argc != 2)
    {
        std::cerr << "Sintaxe: " << argv[0] << " <valor inicial>" << std::endl;
        return -1;
    }

    int x = atoi(argv[1]);
    while (x <= 1000 && std::cout << x++ << std::endl) {}
    return 0;
}
  • This code I made for another post. In which it was asked a code that displayed the numbers from 1 to 1000 without utilziar semicolon in the code... Well, what you said, that that value will be standard, really. I commented on that in the other post.

  • Man, don’t take this the wrong way, but this question of yours is another They look pretty much the same to me. Also, you simply ignored all suggestions (in comments and answers to BOTH questions) about what your problem might be.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.