读完f() vs f(void) in C vs C++做的笔记.

C中f()f(void)区别

f()表示函数形参列表是未知的, 编译器不会帮你做检查.

可以看到gcc编译下面程序, 没有警告.

$ cat test.c
#include <stdio.h>

void foo() {
  printf("foo\n");
}

int main(void) {
  foo(1, 2, 3);
}
$ gcc ./test.c -std=c11 -O2 -Wall -Wextra -Werror -o test
$ ./test
foo

f(void)才真正表示函数形参列表为空.

$ cat test.c
#include <stdio.h>

void foo(void) {
  printf("foo\n");
}

int main(void) {
  foo(1, 2, 3);
}
$ gcc ./test.c -std=c11 -O2 -Wall -Wextra -Werror -o test
./test.c: In function ‘main’:
./test.c:8:3: error: too many arguments to function ‘foo’
    8 |   foo(1, 2, 3);
      |   ^~~
./test.c:3:6: note: declared here
    3 | void foo(void) {
      |      ^~~

另外X86-64生成的机器指令也会受到影响. 可以看到下面的程序能够正常执行, fazbaz分别调用foo以及bar.

$ cat ./foo.c
#include <stdio.h>

void foo(void) {
  printf("foo\n");
}

void bar(void) {
  printf("bar\n");
}
$ cat ./test.c
void foo();

void bar(void);

int faz(void) {
  foo();
  return 0;
}

int baz(void) {
  bar();
  return 0;
}

int main(void) {
  faz();
  baz();

  return 0;
}
$ gcc ./test.c ./foo.c -std=c11 -O2 -Wall -Wextra -Werror -o test
$ ./test
foo
bar

观察fazbaz的反汇编结果, 发现调用foo之前需要将eax清零, 这是因为foo的形参列表是未知的, 所以可能foo的定义是变参, 而System V AMD64里调用变参函数时, 需要通过al说明使用了几个向量寄存器传参.

For calls that may call functions that use varargs or stdargs (prototype-less calls or calls to functions containing ellipsis (. . . ) in the declaration) %al is used as hidden argument to specify the number of vector registers used.

$ objdump ./test --disassemble=faz -Mintel
0000000000001170 <faz>:
    1170:       f3 0f 1e fa             endbr64
    1174:       48 83 ec 08             sub    rsp,0x8
    1178:       31 c0                   xor    eax,eax
    117a:       e8 31 00 00 00          call   11b0 <foo>
    117f:       31 c0                   xor    eax,eax
    1181:       48 83 c4 08             add    rsp,0x8
    1185:       c3                      ret
    1186:       66 2e 0f 1f 84 00 00    cs nop WORD PTR [rax+rax*1+0x0]
    118d:       00 00 00

$ objdump ./test --disassemble=baz -Mintel
0000000000001190 <baz>:
    1190:       f3 0f 1e fa             endbr64
    1194:       48 83 ec 08             sub    rsp,0x8
    1198:       e8 33 00 00 00          call   11d0 <bar>
    119d:       31 c0                   xor    eax,eax
    119f:       48 83 c4 08             add    rsp,0x8
    11a3:       c3                      ret
    11a4:       66 2e 0f 1f 84 00 00    cs nop WORD PTR [rax+rax*1+0x0]
    11ab:       00 00 00
    11ae:       66 90                   xchg   ax,ax

gcc支持-Wstrict-prototypes选项, 遇到f()时发出警告.

$ gcc ./test.c ./foo.c -std=c11 -O2 -Wall -Wextra -Wstrict-prototypes -Werror -o test
./test.c:1:1: error: function declaration isn’t a prototype [-Werror=strict-prototypes]
    1 | void foo();
      | ^~~~
cc1: all warnings being treated as errors

C++中f()f(void)

C++中f()f(void)是等价的, C++中支持函数重载, 需要name mangling, 没办法支持C中f()的语义.

References

System V AMD64 ABI