gcc对open(2)支持重载吗

在Linux中,如果man -s2 open, 我们看到两种不同的函数原型声明:

$ man -s2 open
NAME
       open, creat - open and possibly create a file or device

SYNOPSIS
       #include <sys/types.h>
       #include <sys/stat.h>
       #include <fcntl.h>

       int open(const char *pathname, int flags);
       int open(const char *pathname, int flags, mode_t mode);

...<snip>...

大约14年前(刚迈出大学校园没多久),第一次看到这样的声明的时候,我很纳闷,难道gcc能像g++一样支持c++的重载(overload)?当时没搞明白,反正能编译通过,就没深究(当然那时候也没能力深究)。什么是重载?所谓重载,就是函数名相同,但是参数列表不同。

那么为什么gcc支持编译同一个函数名open但是具有不同的参数列表呢?

先用一个小例子证明gcc不支持重载但是g++支持(当然也必须支持)。

o jade.c

 1 int add(int a, int b)
 2 {
 3         return (a + b);
 4 }
 5 
 6 int add(int a, int b, int c)
 7 {
 8         return (a + b + c);
 9 }
10 
11 int main(int argc, char *argv[])
12 {
13         int m = add(1, 2);
14         int n = add(1, 2, 3);
15         return (m + n);
16 }

o 用gcc编译看看,有报错哦

$ gcc -g -Wall -o jade jade.c 
jade.c:6:5: error: conflicting types for ‘add’
 int add(int a, int b, int c)
     ^
jade.c:1:5: note: previous definition of ‘add’ was here
 int add(int a, int b)
     ^
jade.c: In function ‘main’:
jade.c:13:2: error: too few arguments to function ‘add’
  int m = add(1, 2);
  ^
jade.c:6:5: note: declared here
 int add(int a, int b, int c)
     ^

看来gcc真的不支持重载啊, 让一个c编译器去支持c++的语法,怎么可能呢?! 当然没有这种可能。

o 改用g++编译看看

$ g++ -g -Wall -o jade jade.c
$ ./jade 
$ echo $?
9

没得错,g++能正确处理重载函数。那么回到原来的问题,为什么gcc支持编译两种不同的open()函数原型? 还是先写个demo看看。

o foo.c

 1 /*
 2  * Demo to dig out how these two open() as follows are compiled by gcc.
 3  * o int open(const char *pathname, int flags);
 4  * o int open(const char *pathname, int flags, mode_t mode);
 5  */
 6 #include <sys/types.h>
 7 #include <sys/stat.h>
 8 #include <fcntl.h>
 9 #include <unistd.h>
10 
11 int
12 main(int argc, char *argv[])
13 {
14         /* XXX: Do NOT have any error handling to make things simple */
15 
16         if (argc != 3)
17                 return -1;
18 
19         char *file1 = argv[1];
20         char *file2 = argv[2];
21 
22         int fd1 = open(file1, O_RDWR|O_APPEND);
23         int fd2 = open(file2, O_RDWR|O_APPEND|O_CREAT, 0755);
24 
25         write(fd1, "hello\n", 6);
26         write(fd2, "world\n", 6);
27 
28         close(fd1);
29         close(fd2);
30 
31         return 0;
32 }

o 编译并测试

$ gcc -g -Wall -o foo foo.c
$ rm -f /tmp/f1 /tmp/f2
$ echo "world" > /tmp/f1 && cat /tmp/f1
world
$ ls -l /tmp/f1
-rw-r--r-- 1 veli veli 6 Apr 28 15:46 /tmp/f1
$ ./foo /tmp/f1 /tmp/f2
$ ls -l /tmp/f1 /tmp/f2
-rw-r--r-- 1 veli veli 12 Apr 28 15:46 /tmp/f1
-rwxr-xr-x 1 veli veli  6 Apr 28 15:46 /tmp/f2
$ cat /tmp/f1
world
hello
$ cat /tmp/f2
world

神啦,gcc貌似支持重载。请注意,貌似!好了,反汇编看看。

(gdb) set disassembly-flavor intel
(gdb) disas /m main
Dump of assembler code for function main:
13      {
   0x0804847d <+0>:     push   ebp
   0x0804847e <+1>:     mov    ebp,esp
   0x08048480 <+3>:     and    esp,0xfffffff0
   0x08048483 <+6>:     sub    esp,0x20

14              /* XXX: Do NOT have any error handling to make things simple */
15
16              if (argc != 3)
   0x08048486 <+9>:     cmp    DWORD PTR [ebp+0x8],0x3
   0x0804848a <+13>:    je     0x8048496 <main+25>

17                      return -1;
   0x0804848c <+15>:    mov    eax,0xffffffff
   0x08048491 <+20>:    jmp    0x8048537 <main+186>

18
19              char *file1 = argv[1];
   0x08048496 <+25>:    mov    eax,DWORD PTR [ebp+0xc]
   0x08048499 <+28>:    mov    eax,DWORD PTR [eax+0x4]
   0x0804849c <+31>:    mov    DWORD PTR [esp+0x10],eax

20              char *file2 = argv[2];
   0x080484a3 <+38>:    mov    eax,DWORD PTR [eax+0x8]
   0x080484a6 <+41>:    mov    DWORD PTR [esp+0x14],eax

21
22              int fd1 = open(file1, O_RDWR|O_APPEND);
   0x080484aa <+45>:    mov    DWORD PTR [esp+0x4],0x402
   0x080484b2 <+53>:    mov    eax,DWORD PTR [esp+0x10]
   0x080484b6 <+57>:    mov    DWORD PTR [esp],eax
   0x080484b9 <+60>:    call   0x8048340 <open@plt>
   0x080484be <+65>:    mov    DWORD PTR [esp+0x18],eax

23              int fd2 = open(file2, O_RDWR|O_APPEND|O_CREAT, 0755);
   0x080484c2 <+69>:    mov    DWORD PTR [esp+0x8],0x1ed
   0x080484ca <+77>:    mov    DWORD PTR [esp+0x4],0x442
   0x080484d2 <+85>:    mov    eax,DWORD PTR [esp+0x14]
   0x080484d6 <+89>:    mov    DWORD PTR [esp],eax
   0x080484d9 <+92>:    call   0x8048340 <open@plt>
   0x080484de <+97>:    mov    DWORD PTR [esp+0x1c],eax

...<snip>...
30
31              return 0;
   0x08048532 <+181>:   mov    eax,0x0

32      }
   0x08048537 <+186>:   leave
   0x08048538 <+187>:   ret

End of assembler dump.
(gdb)

对于open()函数,L22有两个参数, L23则有三个参数,gcc都能将其优雅地压入栈(stack)中。这里我们就可以大胆地猜测一下了,open()函数一定是支持变参的!接下来就是找证据。用gcc -E foo.c看一看,

1 $ gcc -E foo.c | egrep open
2 extern int open (const char *__file, int __oflag, ...) __attribute__ ((__nonnull__ (1)));
3 extern int openat (int __fd, const char *__file, int __oflag, ...)
4  int fd1 = open(file1, 02|02000);
5  int fd2 = open(file2, 02|02000|0100, 0755);

注意L2行,open() 果然支持变参,Bingo! 而open()的函数原型定义在fcntl.h中,

$ egrep -in " open .*nonnull.*" /usr/include/fcntl.h
146:extern int open (const char *__file, int __oflag, ...) __nonnull ((1));

相比之下,ioctl(2)一看就知道其支持变参。

$ man -s2 ioctl
NAME
       ioctl - control device

SYNOPSIS
       #include <sys/ioctl.h>

       int ioctl(int d, int request, ...);

...<snip>...

小结:

乍一看,open(2)的man page确实给了我们这样一个假象,一个c编译器gcc竟然支持c++的重载,简直太不可思议啦。然而,透过现象看本质,gcc之所以能够友好地编译两种不同的open()函数原型,是因为,gcc不支持也不可能支持c++的重载,但是open()函数原型支持变参。(Aha, 原来是open(2)的man page误导了我!) 另外,如果对ABI有所了解,那么很容易想明白,open(2)作为一种系统调用(syscall),支持变参,合情合理。

posted @ 2017-04-28 16:11  veli  阅读(1051)  评论(0编辑  收藏  举报