BBYR Achieve
返回信息流
这是一条镜像帖。来源:北邮人论坛 / cpp / #87518同步于 2015/6/12
该镜像源已超过 30 天没有更新,可能在源站已被删除。
CPP机器人发帖

函数前加Extern声明有什么作用?

dajingling
2015/6/12镜像同步17 回复
RT,求解。
订阅后,新回复会通过你的通知中心匿名送达。
9 条回复
FromMars机器人#1 · 2015/6/12
一方面是定义 外部/全局 变量,给其他地方引用 我觉得主要是让链接器知道函数在什么地方,毕竟链接器比较笨
dajingling机器人#2 · 2015/6/12
和不加extern有什么区别吗?是不加extern的头文件必须被引用,加了extern头文件可以不被引用吗? 【 在 FromMars 的大作中提到: 】 : 一方面是定义 外部/全局 变量,给其他地方引用 : 我觉得主要是让连接器知道函数在什么地方,毕竟连接器比较笨
FromMars机器人#3 · 2015/6/12
嗯 用得少还是不说了,免得误人子弟 win上编程基本都是AFX_EXT_CLASS或者__declspec(dllexport)申明的动态库类,我也很少用这个关键字了 【 在 dajingling 的大作中提到: 】 : 和不加extern有什么区别吗?是不加extern的头文件必须被引用,加了extern头文件可以不被引用吗?
nuanyangyang机器人#4 · 2015/6/12
其实对于函数来说,你不加extern,它默认就是extern。 函数的linkage只能是extern或者static。 如果是static,那么一个编译单元(.c文件)不能直接通过函数名来引用另一个编译单元里定义的函数。 如果是extern,那么所有的编译单元里,都可以用这个名称来引用这个函数。
nuanyangyang机器人#5 · 2015/6/12
【 在 FromMars 的大作中提到: 】 : 嗯 用得少还是不说了,免得误人子弟 : win上编程基本都是AFX_EXT_CLASS或者__declspec(dllexport)申明的动态库类,我也很少用这个关键字了 这个标注是关于(运行时的)动态链接的,不是(编译之后马上做的)静态链接。C语言对如何装载、动态链接、动态装载没有任何规定。所以只能依靠操作系统的接口了。
FromMars机器人#6 · 2015/6/12
是吗 我觉得模块功能上来说 动态库技术太强大了 几乎完全不需要什么extern关键字,至于模块内部的静态链接,好像C++也不怎么需要(可以用其他方式实现一样的功能),C没有封装或许用的多一点 【 在 nuanyangyang 的大作中提到: 】 : : 这个标注是关于(运行时的)动态链接的,不是(编译之后马上做的)静态链接。C语言对如何装载、动态链接、动态装载没有任何规定。所以只能依靠操作系统的接口了。
dajingling机器人#7 · 2015/6/15
知道了,就是加了Static的函数没法被别的文件include了,即使include了别的文件也可以定义相同的函数名。是吧? 【 在 nuanyangyang 的大作中提到: 】 : 其实对于函数来说,你不加extern,它默认就是extern。 : 函数的linkage只能是extern或者static。 : 如果是static,那么一个编译单元(.c文件)不能直接通过函数名来引用另一个编译单元里定义的函数。 : ...................
linbin机器人#8 · 2015/6/15
函数默认就是extern属性,要不你怎么在其他文件调用它。有些函数如果要防止被其他文件调用的,可以加static,这样可以封装得更好一些。
nuanyangyang机器人#9 · 2015/6/15
【 在 dajingling 的大作中提到: 】 : 知道了,就是加了Static的函数没法被别的文件include了,即使include了别的文件也可以定义相同的函数名。是吧? include是另一回事。 C语言的每个.c文件都是分别编译的。如果你想在一个.c文件里调用另一个.c文件里定义的函数,你只要声明就可以了。比如: // foo.c // 这里定义函数 int square(int n) { return n * n; } // bar.c // 这里声明 int square(int n); // 即使不说,默认就是extern。 int square_sum(int x, int y) { int x2 = square(x); // 在这里调用 int y2 = square(y); // 在这里调用 return x2 * y2; } 编译的时候,分别编译两个文件,然后链接 cc -c -o foo.o foo.c # 编译foo.c,生成foo.o cc -c -o bar.o bar.c # 编译bar.c,生成bar.o cc -o main foo.c bar.c # 链接,生成可执行文件main 编译器看bar.c的时候,发现声明了但没有定义的square函数,会给调用的地方(就是square_sum里的两个地方)留个记号;链接的时候,找到真正定义square的模块(foo.o),然后把这些记号换成square函数真正的地址。 当然,如果要求每次使用都要自己声明,就太麻烦了。想象一下,如果不只是bar.c要用这个square函数,而是几十个.c文件都要使用它,那么每个文件里都要写一遍声明,太麻烦了。 所以,如果写一个模块(比如foo.c),而且里面有可以在外部调用的函数(比如square),那么就可以单独写一个.h文件,里面写入所有的外部可见的函数的声明。比如这样: // foo.h int square(int n); // 声明 // foo.c #include "foo.h" int square(int n) { return n*n; } C语言在编译foo.c的时候,会先预处理。编译器真正看到的代码是: // 编译器看到的代码 int square(int n); // 同一个文件里既声明又定义是没有问题的。 int square(int n) { return n*n; } 真正带来方便的是用户:bar.c。现在有了foo.h,那么bar.c的作者可以这样写: // bar.c #include "foo.h" int square_sum(int x, int y) { int x2 = square(x); // 在这里调用 int y2 = square(y); // 在这里调用 return x2 * y2; } 这样,编译器编译bar.c的时候,实际看到的就是: // 编译器看到的代码 int square(int n); // 这句是#include "foo.h"得到的。 int square_sum(int x, int y) { int x2 = square(x); // 在这里调用 int y2 = square(y); // 在这里调用 return x2 * y2; } 这也就相当于,只要有#include "foo.h",就有了对foo.c里的公共函数的声明。这样简化了开发。 以上就是include的真正来由。 (其实所谓#include <stdio.h>,做的也就是声明标准库里定义的函数。) include本身有自己的问题:编译器每次都要看一遍.h里的所有内容。如果.h文件很多,很大,又被大量的.c文件include,那么编译就会变得非常慢。很多新的语言,比如java, go, rust等,都有更完善的模块系统,而不是用朴素的include。