zoukankan      html  css  js  c++  java
  • 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),支持变参,合情合理。

  • 相关阅读:
    Hard Rock
    Codeforces Round #416 (Div. 2) B. Vladik and Complicated Book
    codeforces 793B. Igor and his way to work
    codeforces 1B Spreadsheets
    HDU 1069 Monkey and Banana
    codeforces 2B The least round way
    【机器学习】 通俗说拟合
    python-八皇后问题
    python-核心知识思维导图
    python-@property 属性
  • 原文地址:https://www.cnblogs.com/idorax/p/6781775.html
Copyright © 2011-2022 走看看