C语言条件分支逻辑的写法抉择:先校验错误vs常规if/else结构的技术探讨
C语言条件分支逻辑的写法抉择:先校验错误vs常规if/else结构的技术探讨
最近我一直在琢磨C语言里写条件逻辑的「正确」打开方式,尤其是错误处理和成功路径的编排——到底应该用常规的if-else结构:把成功逻辑放在if块、错误处理丢去else,还是反过来,先显式校验错误条件,一旦触发就直接返回/终止,把成功逻辑留在主流程里?
先给大家看两个实际的代码例子对比:
版本1:常规if-else结构
BOOL patchStatus = PatchRtlIsValidHandler(hModule); if (patchStatus) { printf("Successfully patched RtlIsValidHandler for x86 SEH support\n"); } else { fprintf(stderr, "[-] Failure patching RtlIsValidHandler!"); return 1; }
版本2:先校验错误,主流程留成功逻辑
BOOL patchStatus = PatchRtlIsValidHandler(hModule); if (!patchStatus) { fprintf(stderr, "[-] Failure patching RtlIsValidHandler!"); return 1; } printf("Successfully patched RtlIsValidHandler for x86 SEH support\n");
为了搞明白这两种写法最终编译出来的汇编/机器码到底有啥差异,我在Godbolt上跑了个简单的测试用例:
测试代码(先判错误的写法)
#include <stdio.h> #include <stdlib.h> int main() { char* p = malloc(1000); if (!p) { printf("error"); } printf("Allocation success! allocated image: %p", p); return 0; }
对应的汇编输出:
main: .Lfunc_begin0: push rbp mov rbp, rsp sub rsp, 16 mov dword ptr [rbp - 4], 0 .Ltmp0: mov edi, 1000 call malloc@PLT mov qword ptr [rbp - 16], rax .Ltmp1: cmp qword ptr [rbp - 16], 0 jne .LBB0_2 .Ltmp2: lea rdi, [rip + .L.str] mov al, 0 call printf@PLT .Ltmp3: .LBB0_2: mov rsi, qword ptr [rbp - 16] lea rdi, [rip + .L.str.1] mov al, 0 call printf@PLT xor eax, eax add rsp, 16 pop rbp ret .Ltmp4: .Lfunc_end0: .L.str: .asciz "error" .L.str.1: .asciz "Allocation success! allocated image: %p"
从这段汇编能看到,编译器把成功路径做成了分支跳转的目标:当指针非空时,直接跳转到成功逻辑的标签.LBB0_2。
测试代码(常规if-else写法)
#include <stdio.h> #include <stdlib.h> int main() { char* p = malloc(1000); if (p) { printf("Allocation success! allocated image: %p", p); } else { printf("error"); } return 0; }
对应的汇编输出:
main: .Lfunc_begin0: push rbp mov rbp, rsp sub rsp, 16 mov dword ptr [rbp - 4], 0 .Ltmp0: mov edi, 1000 call malloc@PLT mov qword ptr [rbp - 16], rax .Ltmp1: cmp qword ptr [rbp - 16], 0 je .LBB0_2 .Ltmp2: mov rsi, qword ptr [rbp - 16] lea rdi, [rip + .L.str] mov al, 0 call printf@PLT jmp .LBB0_3 .Ltmp3: .LBB0_2: lea rdi, [rip + .L.str.1] mov al, 0 call printf@PLT .Ltmp4: .LBB0_3: xor eax, eax add rsp, 16 pop rbp ret .Ltmp5: .Lfunc_end0: .L.str: .asciz "Allocation success! allocated image: %p" .L.str.1: .asciz "error"
这里编译器的处理反过来了:错误路径是需要跳转的分支,成功逻辑直接在主流程里顺序执行——当指针为空时,才跳转到错误处理的标签.LBB0_2。
为了进一步验证哪种模式更贴近实际生产环境的代码,我用x64dbg查看了Windows原生DLL的实现,发现先判空、跳转到错误分支的汇编模式(也就是对应常规if-else写法的汇编输出)反而更常见,比如这段来自系统DLL的指令:
0000019483F4165A | FF15 F0290000 | call qword ptr ds:[<&RtlImageNtHeader>] | 0000019483F41660 | 48:85C0 | test rax,rax | rax:EntryPoint 0000019483F41663 | 74 2F | je 19483F41694 |
我还特意在进程内存里搜索了这个模式的十六进制码0x48, 0x85, 0xC0(对应test rax, rax + 后续的je指令),结果发现大量匹配的条目。
看起来常规if-else写法编译出来的汇编更贴近系统原生代码的模式,但我还是有点纠结——从代码可读性和代码质量的角度来看,哪种写法更值得推荐呢?想听听大家的看法!
内容来源于stack exchange
相关产品推荐
相关产品推荐

