Toby
Toby

Reputation: 10144

Return inside if and outside or in else?

(C) If I have a function that contains an if which, if the condition is true can then return a certain value an then else return a different value. Is it more or less efficient to use an else or not bother?

i.e. ...

int foo (int a) {
    if ((a > 0) && (a < SOME_LIMIT)) {
        b = a //maybe b is some global or something
        return 0;
    } else {
        return 1;
    }
    return 0;
}

or just

int foo (int a) {
    if ((a > 0) && (a < SOME_LIMIT)) {
        b = a //maybe b is some global or something
        return 0;
    }
    return 1;
}

Assume GCC, will the first implementation result in compiled code being any different from the second one?

I need to be as efficient as possible here, so possible reduction of a branch for the else would be nice - but stylistically my inner OCD doesn't like to see a return that isnt 0 or void as the last instruction in a function as its less clear what is going on. So if it will be got rid of anyway then I could leave the else there...

Upvotes: 3

Views: 21201

Answers (6)

wildplasser
wildplasser

Reputation: 44250

int foo (int a) {

     /* Nothing to do: get out of here */
    if (a <= 0 || a >= SOME_LIMIT) return 1;

    b = a; // maybe b is some global or something

    return 0;
}

There are barely any differences with respect to eficiency (the costliest part is the function call plus the return anyway).

For human readers, the least "indented" code (such as the above) is the easiest to read and understand.

BTW The generated assembler also looks pretty minimal to me, and completely equivalent to the if (a >0 && a < LIMIT) form.

        .file   "return.c"
        .text
        .p2align 4,,15
        .globl  foo
        .type   foo, @function
foo:
.LFB0:
        .cfi_startproc
        leal    -1(%rdi), %edx
        movl    $1, %eax
        cmpl    $2998, %edx
        ja      .L2
        movl    %edi, b(%rip)
        xorb    %al, %al
.L2:
        rep
        ret
        .cfi_endproc
.LFE0:
        .size   foo, .-foo
        .globl  b
        .bss
        .align 16
        .type   b, @object
        .size   b, 4
b:
        .zero   4
        .ident  "GCC: (Ubuntu/Linaro 4.6.1-9ubuntu3) 4.6.1"
        .section        .note.GNU-stack,"",@progbits

Upvotes: 1

Greg Prisament
Greg Prisament

Reputation: 2216

You can run gcc with the -O3 -S options to generate optimized assembly code, so you can see (and compare) the optimized assembly. I made the following changes to your sources to make them compile.

File a.c:

int b;                                                                         

int foo (int a) {             
    if ((a > 0) && (a < 5000)) {  
        b = a;                                                    
        return 0;                                                        
    } else {                                                                   
        return 1;             
    }                                       
    return 0;                          
}

File b.c:

int b;                                                                         
int foo (int a) {                                                             
    if ((a > 0) && (a < 5000)) {
        b = a;
        return 0;
    }       
    return 1;                                                                  
}

When compiling a.c with gcc -O3 -S a.c the file a.s is created. On my machine it looks like this:

               .file      "a.c"
               .text
               .p2align 4,,15
               .globl     foo
               .type      foo, @function
foo:
.LFB0:
               .cfi_startproc
               movl       4(%esp), %edx
               movl       $1, %eax
               leal       -1(%edx), %ecx
               cmpl       $4998, %ecx
               ja         .L2
               movl       %edx, b
               xorb       %al, %al
.L2:
               rep
               ret
               .cfi_endproc
.LFE0:
               .size      foo, .-foo
               .comm      b,4,4
               .ident     "GCC: (Ubuntu/Linaro 4.6.1-9ubuntu3) 4.6.1"
               .section   .note.GNU-stack,"",@progbits

When compiling b.c with gcc -O3 -S b.c the file b.s is created. On my machine it looks like this:

               .file      "b.c"
               .text
               .p2align 4,,15
               .globl     foo
               .type      foo, @function
foo:
.LFB0:
               .cfi_startproc
               movl       4(%esp), %edx
               movl       $1, %eax
               leal       -1(%edx), %ecx
               cmpl       $4998, %ecx
               ja         .L2
               movl       %edx, b
               xorb       %al, %al
.L2:
               rep
               ret
               .cfi_endproc
.LFE0:
               .size      foo, .-foo
               .comm      b,4,4
               .ident     "GCC: (Ubuntu/Linaro 4.6.1-9ubuntu3) 4.6.1"
               .section   .note.GNU-stack,"",@progbits

Notice that the assembled implementations of foo: are identical. So, in this case, with this version of GCC, it does not matter which way you write the code.

Upvotes: 9

Nobilis
Nobilis

Reputation: 7448

You won't get to the last return 0; in the first example. I would say your second one is stylistically clearer at the very least because of that. Less code for the same thing is usually a good thing.

With regards to the performance, you can check out the assembly executable if you fancy such a thing or profile the code and see if there's an actual difference. My bet is none that matters.

Finally, if your compiler supports optimisation flags, use them!

Upvotes: 1

user2132859
user2132859

Reputation: 405

I would write it like this...

int foo (int a) {
    if ((a > 0) && (a < SOME_LIMIT)) {
        b = a //maybe b is some global or something
        return 0;
    } else {
        return 1;
    }
}

The whole function is just a boolean to condition the value of b to be bigger than zero and less than some constant. And the if statement conditions the return. There is no need to add a default return to the function. The default return will invalidate the if condition.

Upvotes: 1

Stefano Falasca
Stefano Falasca

Reputation: 9097

Of course it depends on the compiler. I guess every (decent) compiler on the market will produce exactly the same output in both cases. However trying such micro-optimizations is disencouraged by any book about optimization!

Choose the form which is better readable.

Upvotes: 1

KrisSodroski
KrisSodroski

Reputation: 2842

Check the object file between both implementations. Put an assembly header such as

 PortionInQuestion: 

That will then show up in your assembly file as a label and you can see how the assembly generated is different. They might not be different at all (because of the optimizations), or they could be completely different. Without seeing hte raw assembly, there's no way to tell how the compiler is optimizing it.

Upvotes: 1

Related Questions