简体   繁体   中英

'if' test condition in c - does it evaluate?

When calling a function in the test portion of an if statement in c, does it evaluate exactly as if you had called it normally? As in, will all the effects besides the return value evaluate and persist?

For example, if I want to include an error check when calling fseek, can I write

if( fseek(file, 0, SEEK_END) ) {fprintf(stderr, "File too long")};

and be functionally the same as:

long int i = fseek(file, 0, SEEK_END); 
if( i ) {fprintf(stderr, "File too long")};

?

https://www.gnu.org/software/gnu-c-manual/gnu-c-manual.html#The-if-Statement

https://www.gnu.org/software/libc/manual/html_node/File-Positioning.html

Yes, this is exactly the same. This only difference is you won't be able to use again the result of the operation executed in the if statement.

In both cases the operation is being executed BEFORE the condition (comparison) happens. To illustrate this, we can see what is the result of the two different cases in machine code. Please do note that the output machine code will vary depending of the OS and compiler.

Source file 'a.c':

#include <stdio.h>

int
main(void)
{
    FILE *f = fopen("testfile", "r");
    long int i = fseek(f, 0, SEEK_END);

    if (i)
        fprintf(stderr, "Error\n");
    return 0;
}

$ gcc -O1 a.c -oa

Source file 'b.c':

#include <stdio.h>

int
main(void)
{
    FILE *f = fopen("testfile", "r");

    if (fseek(f, 0, SEEK_END))
        fprintf(stderr, "Error\n");
    return 0;
}

$ gcc -O1 b.c -ob

You will note that for both cases I used the option '-O1' which allows the compiler to introduce small optimizations, this is mostly to make the machine code a little cleaner as without optimization the compiler converts "literally" to machine code.

$ objdump -Mintel -D a |grep -i main -A20

0000000000001189 <main>:
    1189:   f3 0f 1e fa             endbr64 
    118d:   48 83 ec 08             sub    rsp,0x8
    1191:   48 8d 35 6c 0e 00 00    lea    rsi,[rip+0xe6c]        # 2004 <_IO_stdin_used+0x4>
    1198:   48 8d 3d 67 0e 00 00    lea    rdi,[rip+0xe67]        # 2006 <_IO_stdin_used+0x6>
    119f:   e8 dc fe ff ff          call   1080 <fopen@plt>

# Interesting part
    11a4:   48 89 c7                mov    rdi,rax # Sets return of fopen as param 1
    11a7:   ba 02 00 00 00          mov    edx,0x2 # Sets Ox2 (SEEK_END) as param 3
    11ac:   be 00 00 00 00          mov    esi,0x0 # Sets 0 as param 2
    11b1:   e8 ba fe ff ff          call   1070 <fseek@plt> # Call to FSEEK being made and stored in register
    11b6:   85 c0                   test   eax,eax # Comparison being made
    11b8:   75 0a                   jne    11c4 <main+0x3b> # Comparison jumping
# End of interesting part

    11ba:   b8 00 00 00 00          mov    eax,0x0
    11bf:   48 83 c4 08             add    rsp,0x8
    11c3:   c3                      ret    
    11c4:   48 8b 0d 55 2e 00 00    mov    rcx,QWORD PTR [rip+0x2e55]        # 4020 <stderr@@GLIBC_2.2.5>
    11cb:   ba 06 00 00 00          mov    edx,0x6
    11d0:   be 01 00 00 00          mov    esi,0x1
    11d5:   48 8d 3d 33 0e 00 00    lea    rdi,[rip+0xe33]        # 200f <_IO_stdin_used+0xf>
    11dc:   e8 af fe ff ff          call   1090 <fwrite@plt>
    11e1:   eb d7                   jmp    11ba <main+0x31>
    11e3:   66 2e 0f 1f 84 00 00    nop    WORD PTR cs:[rax+rax*1+0x0]
    11ea:   00 00 00 
    11ed:   0f 1f 00                nop    DWORD PTR [rax]

Objdumping on binary 'b' yields an almost identical same machine code result. To sum it up, whatever you put in your if statement is evaluated and will yield a beind-the-scene equivalent result whether or not you assign it a variable first.

Edit:

For reference, this is the output of $ objdump -Mintel -D b |grep -i main -A20 :

0000000000001189 <main>:
    1189:   f3 0f 1e fa             endbr64 
    118d:   48 83 ec 08             sub    rsp,0x8
    1191:   48 8d 35 6c 0e 00 00    lea    rsi,[rip+0xe6c]        # 2004 <_IO_stdin_used+0x4>
    1198:   48 8d 3d 67 0e 00 00    lea    rdi,[rip+0xe67]        # 2006 <_IO_stdin_used+0x6>
    119f:   e8 dc fe ff ff          call   1080 <fopen@plt>

# Interesting Part
    11a4:   48 89 c7                mov    rdi,rax
    11a7:   ba 02 00 00 00          mov    edx,0x2
    11ac:   be 00 00 00 00          mov    esi,0x0
    11b1:   e8 ba fe ff ff          call   1070 <fseek@plt>
    11b6:   85 c0                   test   eax,eax
    11b8:   75 0a                   jne    11c4 <main+0x3b>
# End of interesting part

    11ba:   b8 00 00 00 00          mov    eax,0x0
    11bf:   48 83 c4 08             add    rsp,0x8
    11c3:   c3                      ret    
    11c4:   48 8b 0d 55 2e 00 00    mov    rcx,QWORD PTR [rip+0x2e55]        # 4020 <stderr@@GLIBC_2.2.5>
    11cb:   ba 06 00 00 00          mov    edx,0x6
    11d0:   be 01 00 00 00          mov    esi,0x1
    11d5:   48 8d 3d 33 0e 00 00    lea    rdi,[rip+0xe33]        # 200f <_IO_stdin_used+0xf>
    11dc:   e8 af fe ff ff          call   1090 <fwrite@plt>
    11e1:   eb d7                   jmp    11ba <main+0x31>
    11e3:   66 2e 0f 1f 84 00 00    nop    WORD PTR cs:[rax+rax*1+0x0]
    11ea:   00 00 00 
    11ed:   0f 1f 00                nop    DWORD PTR [rax]

The short answer is yes (as in your trivial example), the long answer is maybe.

When the logical expression (any) is more complex the C language evaluates it until the result of the whole expression is fully determined. The remaining operations are not evaluated.

Examples:

int x = 0;

if(x && foo()) {} 

the foo will not be called because x is false - and then the whole operation is false.

int x = 1;

if(x && foo()) {} 

the foo will be called because x is true and the second part of the expression is needed to get the result.

It is called Short circuit evaluation and all logical expressions in C are evaluated this way.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM