• 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
    ", 6);
    26         write(fd2, "world
    ", 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),支持变参,合情合理。

  • 相关阅读:
    Oracle 的日期类型
    简单的同步Socket程序服务端
    MMORPG中的相机跟随算法
    使用了UnityEditor中的API,打包时却不能打包UnityEditor的问题
    C# 中的关键字整理
    Unity3D C#中使用LINQ查询(与 SQL的区别)
    C# 值类型与引用类型的异同
    Unity3D NGUI事件监听的综合管理
    Unity3D 动画状态机简单控制核心代码
    Unity3D判断触摸方向
  • 原文地址:https://www.cnblogs.com/idorax/p/6781775.html
Copyright © 2020-2023  润新知