可怕的extern关键字一、不利之处二、例子三、分析四、正确做法

可怕的extern关键字一、不利之处二、例子三、分析四、正确做法

实际项目中看到有人用extern关键字来声明外部函数,这是一个很不好的行为。

一、不利之处如果函数原型改变的话,每个extern声明的地方都要改一遍。

如果有地方没改到呢?

我们通过一个例子来看下悲剧是怎么发生的。

二、例子头文件api.h中声明了一个函数func:

代码语言:javascript代码运行次数:0运行复制#ifndef __API_H__

#define __API_H__

void func(int a);

#endif文件api.c中实现了func函数:

代码语言:javascript代码运行次数:0运行复制#include

void func(int a)

{

printf("hello world.[%d]\n", a);

}文件bad_test.c中调用了func函数,但是func被重新声明成无参数的了

代码语言:javascript代码运行次数:0运行复制#include

extern void func();

int main(int argc,char *argv[])

{

func();

return 0;

}编译运行结果如下:

代码语言:javascript代码运行次数:0运行复制$ gcc -Wall bad_test.c api.c

$ ./a.out

hello world.[1]

$ 三、分析编译的时候即时加了-Wall选项也没有编译警告。

这是因为编译是以源文件为单位的,在bad_test.c中func的声明是无参的,调用也是按无参调用的,所以编译器不会告警。

如果把extern声明去掉,编译器好歹还能给个“函数未显式定义”的警告。 链接的时候也没报错?

这是因为,在C语言中,编译出来的函数符号表是不带参数的,如下所示, func在符号表中就是字符串func。

这也是为啥C语言不能做编译时多态的原因。

所以,别指望在链接的时候报错。

$ gcc -c api.c $ nm api.o 0000000000000000 T func U printf $

程序竟然还能运行???

程序输出了1, 但是这个1是哪里来的呢?

我们先看看下面这一系列输入输出: $ ./a.out hello world.[1] $ ./a.out a hello world.[2] $ ./a.out a b hello world.[3] $ ./a.out a b c hello world.[4] $ ./a.out a b c d hello world.[5]

看明白了吗? 竟然把argc的值打了出来!!!

程序运行的时候,调用的肯定还是带参数的func函数,但是这个参数从哪里来呢?

考虑到默认从右到左的压栈顺序,处于栈顶的argc被取出来塞给func函数作为入参了,所以func打印出来的是argc的值。

都这样了,接下来离各种莫名其妙的异常还远吗?

这种问题定位起来会搞死人的。

四、正确做法建议通过头文件引用的方式来使用外部函数,如果bad_test.c写成下面这样,编译就无法通过,可以有效阻止错误蔓延。

代码语言:javascript代码运行次数:0运行复制#include

#include "api.h"

int main(int argc,char *argv[])

{

func();

return 0;

}编译报错:

代码语言:javascript代码运行次数:0运行复制$ gcc bad_test.c api.c -c

bad_test.c: In function ‘main’:

bad_test.c:6:5: error: too few arguments to function ‘func’

func();

^

In file included from bad_test.c:2:0:

api.h:3:6: note: declared here

void func(int a);

^

$

相关

一个冰柜24小时用多少电-一个冰柜24小时用多少电费
365直播网APP下载

一个冰柜24小时用多少电-一个冰柜24小时用多少电费

📅 07-02 👁️ 9736
2、梦幻109五庄点灯多少血
beat365英超欧冠平台

2、梦幻109五庄点灯多少血

📅 07-07 👁️ 5486
阿里巴巴IP地址范围
365直播网APP下载

阿里巴巴IP地址范围

📅 06-27 👁️ 4333