在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

 int add(int a, int b)
{
return (a + b);
} int add(int a, int b, int c)
{
return (a + b + c);
} int main(int argc, char *argv[])
{
int m = add(, );
int n = add(, , );
return (m + n);
}

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

$ gcc -g -Wall -o jade jade.c
jade.c::: error: conflicting types for ‘add’
int add(int a, int b, int c)
^
jade.c::: note: previous definition of ‘add’ was here
int add(int a, int b)
^
jade.c: In function ‘main’:
jade.c::: error: too few arguments to function ‘add’
int m = add(, );
^
jade.c::: 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 $?

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

o foo.c

 /*
* Demo to dig out how these two open() as follows are compiled by gcc.
* o int open(const char *pathname, int flags);
* o int open(const char *pathname, int flags, mode_t mode);
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h> int
main(int argc, char *argv[])
{
/* XXX: Do NOT have any error handling to make things simple */ if (argc != )
return -; char *file1 = argv[];
char *file2 = argv[]; int fd1 = open(file1, O_RDWR|O_APPEND);
int fd2 = open(file2, O_RDWR|O_APPEND|O_CREAT, ); write(fd1, "hello\n", );
write(fd2, "world\n", ); close(fd1);
close(fd2); return ;
}

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-- veli veli Apr : /tmp/f1
$ ./foo /tmp/f1 /tmp/f2
$ ls -l /tmp/f1 /tmp/f2
-rw-r--r-- veli veli Apr : /tmp/f1
-rwxr-xr-x veli veli Apr : /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:
{
0x0804847d <+>: push ebp
0x0804847e <+>: mov ebp,esp
0x08048480 <+>: and esp,0xfffffff0
0x08048483 <+>: sub esp,0x20 /* XXX: Do NOT have any error handling to make things simple */ if (argc != )
0x08048486 <+>: cmp DWORD PTR [ebp+0x8],0x3
0x0804848a <+>: je 0x8048496 <main+> return -;
0x0804848c <+>: mov eax,0xffffffff
0x08048491 <+>: jmp 0x8048537 <main+> char *file1 = argv[];
0x08048496 <+>: mov eax,DWORD PTR [ebp+0xc]
0x08048499 <+>: mov eax,DWORD PTR [eax+0x4]
0x0804849c <+>: mov DWORD PTR [esp+0x10],eax char *file2 = argv[];
0x080484a3 <+>: mov eax,DWORD PTR [eax+0x8]
0x080484a6 <+>: mov DWORD PTR [esp+0x14],eax int fd1 = open(file1, O_RDWR|O_APPEND);
0x080484aa <+>: mov DWORD PTR [esp+0x4],0x402
0x080484b2 <+>: mov eax,DWORD PTR [esp+0x10]
0x080484b6 <+>: mov DWORD PTR [esp],eax
0x080484b9 <+>: call 0x8048340 <open@plt>
0x080484be <+>: mov DWORD PTR [esp+0x18],eax int fd2 = open(file2, O_RDWR|O_APPEND|O_CREAT, );
0x080484c2 <+>: mov DWORD PTR [esp+0x8],0x1ed
0x080484ca <+>: mov DWORD PTR [esp+0x4],0x442
0x080484d2 <+>: mov eax,DWORD PTR [esp+0x14]
0x080484d6 <+>: mov DWORD PTR [esp],eax
0x080484d9 <+>: call 0x8048340 <open@plt>
0x080484de <+>: mov DWORD PTR [esp+0x1c],eax ...<snip>... return ;
0x08048532 <+>: mov eax,0x0 }
0x08048537 <+>: leave
0x08048538 <+>: ret End of assembler dump.
(gdb)

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

 $ gcc -E foo.c | egrep open
extern int open (const char *__file, int __oflag, ...) __attribute__ ((__nonnull__ ()));
extern int openat (int __fd, const char *__file, int __oflag, ...)
int fd1 = open(file1, |);
int fd2 = open(file2, ||, );

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

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

相比之下,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),支持变参,合情合理。

gcc对open(2)支持重载吗的更多相关文章

  1. C++中对封装的语法支持——重载运算符

    重载运算符 1.对于自定义类型,编译器不知道运算规则,而重载运算符会将两个对象相加转换为函数调用. 2.运算符重载转换的函数调用,函数名字是固定的规则. (1) 如果重载+号运算符,函数名字就是:op ...

  2. gcc对c++标准的支持

    GCC 4.8.1完全支持c++11核心部分,对应的glibc为2.17 gcc 4.9支持c++11正则表达式,卧槽...4.8.5会报terminate called after throwing ...

  3. 交叉编译支持SVE ACLE的gcc

    最近在学习AArch64的SVE技术时,发现目前可以在网上找到的gcc版本都不支持SVE intrinsic方式调用,在看文档时发现,GCC要到2020年的GCC10时才会支持: 在github上看到 ...

  4. gcc,g++,extern “C” :一些编译错误的缘由

    正好是我们代码中遇到的问题,之前不求甚解,只用g++编译没有错误就不管了,现在要跨平台到windows下就遇到了问题.全文转载自:http://user.qzone.qq.com/75172588/b ...

  5. c++的重载 缺省参数和命名空间详解

    参加了几次笔试,发现有很多c++方面的问题被卡了.从现在开始进攻c++.之后会陆续更新c++学习笔记. 先说说我学习的书籍,大家如果有好的书籍推荐,感谢留言. 暂时是在看这些书自学. 1.C++介绍. ...

  6. boost::bind 不能处理函数重载 (error: no matching function for call to 'bind')

    前言 最近任务多.工期紧,没有时间更新博客,就水一期吧.虽然是水,也不能太失水准,刚好最近工作中遇到一个 boost::bind 的问题,花费了半天时间来定位解决,就说说它吧. 问题背景 项目中使用了 ...

  7. GCC 预处理、编译、汇编、链接..

    1简介 GCC 的意思也只是 GNU C Compiler 而已.经过了这么多年的发展,GCC 已经不仅仅能支持 C 语言:它现在还支持 Ada 语言.C++ 语言.Java 语言.Objective ...

  8. Linux GCC常用命令

    1简介 2简单编译 2.1预处理 2.2编译为汇编代码(Compilation) 2.3汇编(Assembly) 2.4连接(Linking) 3多个程序文件的编译 4检错 5库文件连接 5.1编译成 ...

  9. PHP面向对象编程——深入理解方法重载与方法覆盖(多态)

    什么是多态? 多态(Polymorphism)按字面的意思就是“多种状态”.在面向对象语言中,接口的多种不同的实现方式即为多态.引用Charlie Calverts对多态的描述——多态性是允许你将父对 ...

随机推荐

  1. Android-自定义ViewPager

    效果图: 布局去指定自定义ViewPager: view.custom.shangguigucustomview.MyCustomViewPager <!-- 仿viewpager --> ...

  2. SQLSERVER带端口号的链接方式

    SQLSERVER带端口号的链接方式 <add key="BBAcnn" value="server=IP,端口号\实例名;database=TESTDB;uid= ...

  3. GlusterFS 一

    GlusterFS 一 1 安装源 yum install centos-release-gluster312.noarch 列出所有可用安装包yum list gluster* 安装glusterf ...

  4. VUE 学习笔记 一 指令

    1.声明式渲染 v-bind 特性被称为指令.指令带有前缀 v-,以表示它们是 Vue 提供的特殊特性 <div id='app'> <span v-bind:title=" ...

  5. 论 BUG调试与(程序猿)初学者

    作为一枚程序猿,BUG调试是最基本的技能,对于初学者更是重中之重.个人而言,要想为自己的程序猿生涯更上一层楼,就得知道什么是BUG调试,而且还必须知道怎么调好BUG.那么BUG究竟是什么呢?在我之前的 ...

  6. asp.net Frameset框架集的嵌套使用

    <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Frame.aspx.cs& ...

  7. c#设计模式之代理模式(Proxy Pattern)

    引言 代理这个词语,大家在现实世界已经频繁的接触过,例如火车站代理售票点,因为这些代理售票点的存在,我们不必要去火车站的售票处就可以查询或者取到火车票.代理点本身是没有能力生产车票的,我们在代理处享受 ...

  8. C#获取图片扩展名

    代码: private string GetImageExtension(MemoryStream ms) { try { Image image = Image.FromStream(ms); if ...

  9. Linq的Join == 两个foreach

    因为实在太懒了,很久没动笔,今天强迫自己写一个小短篇. 之前讨论过用SelectMany代替两重的foreach循环.今天我们看一下Join和foreach的关系. 首先是Join的定义 public ...

  10. 大公司怎么做Android代码混淆的?

    3月17日,网易资深安全工程师钟亚平在安卓巴士全球开发者论坛上做了<安卓APP逆向与保护>的演讲.其中就谈到了关于代码混淆的问题.现摘取部分重点介绍如下:   Java代码是非常容易反编译 ...