C++ 编译时会把支持c11标准的编译器库里所有的函数都编译吗

7204人阅读
最近,有同事向我多次问及C++关于编译链接方面的问题,包括如下:
1:什么样的函数以及变量可以定义在头文件中
2:extern &C&的作用
3:防止重复包含的宏的作用
4:函数之间是怎么链接起来的
我认为,这些问题不难,书上基本上都有,但要是没有真正思考过,就凭死记硬背,也就是只能“嘴上说说”而已,遇到问题还真棘手,所以我觉得有必要说一下。
C/C++的编译链接过程
其实,“编译”这个词大多数时候,我们指的是由一堆.h,.c,.cpp文件生成链接库或者可执行文件的过程。但是拿C/C++来说,其实这是很模糊的,由一堆C/C++文件生成应用程序包括预处理---编译文件---链接(写的比较粗糙,不影响本文论述)。
首先,要明白什么是编译单元,一个编译单元可以认为是一个.c或者.cpp文件,每一个编译单元首先会经过预处理得到一个临时的编译单元,这里称为tmp.cpp,预处理会把.c或者.cpp直接或者间接包含的其它文件(不只局限于.h文件,只要是#include即可)的内容替换进来,并展开宏调用等。
下面首先看一个例子:
#ifndef A_H_
#define A_H_
static int a = 1;
void fun();
#endifa.cpp
#include &a.h&
static void hello_world()
}只有a.h和a.cpp这两个文件,及其简单。首先通过g++的-E参数得到a.cpp预处理之后的内容
coderchen@coderchen:~/c++$ g++ -E a.cpp & tmp.cpp查看tmp.cpp
# 1 &a.cpp&
# 1 &&built-in&&
# 1 &&command-line&&
# 1 &a.cpp&
# 1 &a.h& 1
static int a = 1;
void fun();
# 2 &a.cpp& 2
static void hello_world()
}tmp.cpp就是只经过预处理得到的文件,这个文件才是编译器能够真正看到的文件。这个过程就是预处理。
其中#define A_H_的作用是防止重复包含a.h这个头文件,很多人都知道这一点,但是再仔细问,我见过大多数人都说不清楚。
这种宏是为了防止一个编译单元(cpp文件)重复包含同一个头文件。它在预处理阶段起作用,预处理器发现a.cpp内已经定义过A_H_这个宏的话,在a.cpp中再次发现#include &a.h&的时候就不会把a.h的内容替换进a.cpp了。
编译器看到tmp.cpp的时候,会编译成一个obj文件,最后由链接器对这一个对obj文件进行链接,从而得到可执行程序。
编译错误和连接错误
编译错误指的是一个cpp编译单元在编译时发生的错误,这种错误一般都是语法错误,拼写错误,参数不匹配等。
以main.cpp为例(只有一个main函数)
int main()
hello_world();
编译(加-c参数表示只编译不链接)
coderchen@coderchen:~/c++$ g++ -c -o main.o main.cpp
main.cpp: In function ‘int main()’:
main.cpp:4: error: ‘hello_world’ was not declared in this scope这种错误就是编译,原因是hello_world函数未声明,把void hello_world();这条语句加到main函数前面,再次编译
coderchen@coderchen:~/c++$ g++ -c -o main.o main.cpp
coderchen@coderchen:~/c++$ 编译成功,虽然我们调用了hello_world函数,却没有定义这个函数。好,接下来,我们把这个main.o文件链接下,
coderchen@coderchen:~/c++$ g++ -o main main.o
main.o: In function `main':
main.cpp:(.text+0x7): undefined reference to `hello_world()'
collect2: ld returned 1 exit status看到了吧,链接器ld报出了链接错误,原因是hello_world这个函数找不到。这个例子很简单,基本上可以区分出编译错误和链接错误。我们再添加一个hello_world.cpp
void hello_world()
coderchen@coderchen:~/c++$ g++ -c -o hello_world.o hello_world.cpp链接
coderchen@coderchen:~/c++之所以$ g++ -o main main.o hello_world.ook,我们的main程序已经生成了,我们经历了预处理---编译---链接的过程。
有的人说为什么不需要写一个hello_world.h的头文件,声明hello_world函数,然后再让main.cpp包含hello_world.h呢?这样写自然是标准的做法,不过预处理过后,和我们现在写的一样的,预处理会把hello_world.h的内容替换到main.cpp中。
问题:在链接的时候,main.o怎么知道hello_world函数定义在hello_world.o中呢?
答案:main.o不知道hello_world函数定义在那个obj文件中,每个obj文件都有一个导出符号表,对于这个例子,hello_world.o的导出符号表中有hello_world这个函数,而main.o需要用到这个函数,可以想象就像几个插槽一样。链接器通过扫描obj文件发现这个函数定义在hello_world.o中,然后就可以链接了。
问题:为什么函数不能定义在头文件中?
这个问题是不恰当的,因为用inline和static修饰的函数可以定义在头文件中,而inline修饰的函数必须定义在头文件中。
如果函数定义在头文件中,并且有多个cpp文件都包含了这个头文件的话,那么这些cpp文件生成的obj文件的导出符号表中都有这个头文件中定义的函数,单文件编译的时候是不会出错的,但是链接的时候就会报错。链接器发现了多个函数实体,但却无法确定应该使用哪一个。这是一个链接错误。
inline修饰的函数,通常都不会存在函数实体,即便编译器没有对其内联,那么obj文件也不会导出inline函数,所以链接不会出错。
static修饰的函数,只能由定义它的编译单元调用,也不会导出。如果头文件中顶一个static修饰的函数,就相当于多个obj文件中都顶一个了一个一模一样的函数,大家各用各的,互补干扰。
问题:什么样的变量可以定义在头文件中?
其实变量于函数很类似,由static或const修饰的变量可以定义在头文件中。
static修饰的变量于static修饰的函数一样,道理同上。
const修饰的变量默认是不会进入导出符号表的,相当于每个obj中都定义了一个一模一样的const变量,各用各的。而const可以再用extern修饰,如果用extern const修饰的变量定义在头文件中,那么就会出现链接错误,原因就是“想一想extern是干嘛的”
问题:extern &C&是干嘛的?
如果有人回答“兼容C和C++”,我只能说“这是一个正确答案,但我不知道你是否真的知道”。
首先要知道C不支持重载,C++支持重载,C++为了支持重载,引入了函数重命名的机制,就像下面这样:
int hello_world(type1 param);
int hello_world(type2 param);通常第一个函数会被编译成hello_world_type1这样子,第二个函数会被编译成hello_world_type2这样子。不管是定义的地方还是调用的地方,都会把函数改成同样的名字,所以链接器可以正确的找到函数实体。
而我们写C++程序的时候,通常会引入由c编写的库(gcc编译的c文件),而c不支持重载,自然不会对函数重命名。而我们在C++中调用的地方很可能会重命名,这就造成了调用的地方(C++编译)和定义的地方(C编译)函数名不一致的情况,这也是一种链接错误。
所以我们经常会看到在C++中用extern &C& { #include &some_c.h& }这种代码。这就是告诉c++编译器,some_c.h中的函数要按照c的方式编译,不要重命名,这样在链接的时候就ok了。
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:297091次
积分:3644
积分:3644
排名:第7506名
原创:94篇
转载:11篇
评论:81条
(3)(9)(3)(4)(6)(4)(3)(6)(3)(10)(4)(8)(15)(7)(20)2012年11月 Linux/Unix社区大版内专家分月排行榜第二2011年8月 Linux/Unix社区大版内专家分月排行榜第二2008年10月 C/C++大版内专家分月排行榜第二
2012年8月 Linux/Unix社区大版内专家分月排行榜第三
2013年 总版技术专家分年内排行榜第三
2012年 总版技术专家分年内排行榜第七
2013年 总版技术专家分年内排行榜第三
2012年 总版技术专家分年内排行榜第七
2013年 总版技术专家分年内排行榜第三
2012年 总版技术专家分年内排行榜第七
匿名用户不能发表回复!|
每天回帖即可获得10分可用分!小技巧:
你还可以输入10000个字符
(Ctrl+Enter)
请遵守CSDN,不得违反国家法律法规。
转载文章请注明出自“CSDN(www.csdn.net)”。如是商业用途请联系原作者。C++/C试题的答案与评分标准
C++/C试题的答案与评分标准
一、请填写BOOL&,&float,&指针变量与“零值”比较的&if&语句。(10分)&
请写出&BOOL&flag&与“零值”比较的&if&语句。(3分)&
标准答案:&
if&(&flag&)&
if&(&!flag&)&
如下写法均属不良风格,不得分。&
if&(flag&==&TRUE)&
if&(flag&==&1&)&
if&(flag&==&FALSE)&
if&(flag&==&0)&
请写出&float&x&与“零值”比较的&if&语句。(4分)&
标准答案示例:&
const&float&EPSINON&=&0.00001;&
if&((x&&=&-&EPSINON)&&&&(x&&=&EPSINON)
不可将浮点变量用“==”或“!=”与数字比较,应该设法转化成“&=”或“&=”此类形式。&
如下是错误的写法,不得分。&
if&(x&==&0.0)&
if&(x&!=&0.0&
请写出&char&*p&与“零值”比较的&if&语句。(3分)&
标准答案:&
if&(p&==&NULL)&
if&(p&!=&NULL)&
如下写法均属不良风格,不得分。&
if&(p&==&0)&
if&(p&!=&0)&
二、以下为Windows&NT下的32位C++程序,请计算sizeof的值(10分)&
char&str[]&=&“Hello”&;&
char&*p&=&str&;&
int&n&=&10;&
sizeof&(str&)&=&6&(2分)&
sizeof&(&p&)&=&4&(2分)&
sizeof&(&n&)&=&4&(2分)&
void&Func&(&char&str[100])&
sizeof(&str&)&=&4&(2分)&
.对于函数参数列表中的
以数组类型书写的形式参数,
编译器把其解释为普通
的指针类型,即char
void&*p&=&malloc(&100&);&
sizeof&(&p&)&=&4&(2分)&
三、简答题(25分)&
1、头文件中的&ifndef/define/endif&干什么用?(5分)&
答:防止该头文件被重复引用。
2、#include&&filename.h&&和&#include&“filename.h”&有什么区别?(5分)&
答:对于#include&&filename.h&&,编译器从标准库路径开始搜索&filename.h&对于#include&“filename.h”&,编译器从用户的工作路径开始搜索&filename.h&
3、const&有什么用途?(请至少说明两种)(5分)&
答:(1)可以定义&const&常量
(2)const可以修饰函数的参数、返回值,甚至函数的定义体。被const修饰的东西都受到强制保护,可以预防意外的变动,能提高程序的健壮性。
4、在C++&程序中调用被&C编译器编译后的函数,为什么要加&extern&“C”?&(5分)&
答:C++语言支持函数重载,C语言不支持函数重载。函数被C++编译后在库中的名字与C语言的不同。假设某个函数的原型为:&void&foo(int&x,&int&y);该函数被C编译器编译后在库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字。C++提供了C连接交换指定符号extern“C”来解决名字匹配问题。
5、请简述以下两个for循环的优缺点(5分)&
for&(i=0;&i&N;&i++)&
if&(condition)&
DoSomething();&
DoOtherthing();&
if&(condition)&
for&(i=0;&i&N;&i++)&
DoSomething();&
for&(i=0;&i&N;&i++)&
DoOtherthing();&
优点:程序简洁&
缺点:多执行了N-1次逻辑判断,并且打断了循环“流水线”作业,使得编译器不能对循环进行优化处理,降低了效率。&
优点:循环的效率高&
缺点:程序不简洁&
四、有关内存的思考题(每小题5分,共20分)&
void&GetMemory(char&*p)&
p&=&(char&*)malloc(100);&
void&Test(void)&
char&*str&=&NULL;&
GetMemory(str);&
strcpy(str,&&hello&world&);&
printf(str);&
请问运行Test函数会有什么样的结果?&
答:程序崩溃。
因为GetMemory并不能传递动态内存,&
Test函数中的&str一直都是&NULL。
strcpy(str,&&hello&world&);将使程序崩溃。
char&*GetMemory(void)&
char&p[]&=&&hello&world&;&
return&p;&
void&Test(void)&
char&*str&=&NULL;&
str&=&GetMemory();&
printf(str);&
请问运行Test函数会有什么样的结果?&
答:可能是乱码。
因为GetMemory返回的是指向“栈内存”的指针,该指针的地址不是&NULL,但其原现的内容已经被清除,新内容不可知。
void&GetMemory2(char&**p,&int&num)&
*p&=&(char&*)malloc(num);&
void&Test(void)&
char&*str&=&NULL;&
GetMemory(&str,&100);&
strcpy(str,&&hello&);&
printf(str);&
请问运行Test函数会有什么样的结果?&
(1)能够输出hello&
(2)内存泄漏,str指针使用完后未释放
void&Test(void)&
char&*str&=&(char&*)&malloc(100);&
strcpy(str,&“hello”);&
free(str);&
if(str&!=&NULL)&
strcpy(str,&“world”);&
printf(str);&
请问运行Test函数会有什么样的结果?&
答:篡改动态内存区的内容,后果难以预料,非常危险。
因为free(str);之后,str成为野指针,&
if(str&!=&NULL)语句不起作用。&
五、编写strcpy函数(10分)&
已知strcpy函数的原型是&
char&*strcpy(char&*strDest,&const&char&*strSrc);&
其中strDest是目的字符串,strSrc是源字符串。
(1)不调用C++/C的字符串库函数,请编写函数&strcpy&
char&*strcpy(char&*strDest,&const&char&*strSrc);&
assert((strDest!=NULL)&&&&(strSrc&!=NULL));&//&2分&
char&*address&=&strD&//&2分&
while(&(*strDest++&=&*&strSrc++)&!=&‘\0’&)&//&2分&
return&address&;&//&2分&
(2)strcpy能把strSrc的内容复制到strDest,为什么还要char&*&类型的返回值?&
答:为了实现链式表达式。&//&2分
例如&int&length&=&strlen(&strcpy(&strDest,&“hello&world”)&);&
六、编写类String的构造函数、析构函数和赋值函数(25分)&
已知类String的原型为:&
class String&
String(const char *str = NULL); // 普通构造函数&
String(const String &other); // 拷贝构造函数&
~ String(void); // 析构函数&
String & operate =(const String &other); // 赋值函数&
char *m_ // 用于保存字符串&
请编写String的上述4个函数。
标准答案:&
// String的析构函数&
String::~String(void) // 3分&
delete [] m_&
// 由于m_data是内部数据类型,也可以写成 delete m_&
// String的普通构造函数&
String::String(const char *str) // 6分&
if(str==NULL)&
m_data = new char[1]; // 若能加 NULL 判断则更好&
*m_data = ‘\0’;&
int length = strlen(str);&
m_data = new char[length+1]; // 若能加 NULL 判断则更好&
strcpy(m_data, str);&
// 拷贝构造函数&
String::String(const String &other) // 3分&
int length = strlen(other.m_data);&
m_data = new char[length+1]; // 若能加 NULL 判断则更好&
strcpy(m_data, other.m_data);&
// 赋值函数&
String & String::operate =(const String &other) // 13分&
// (1) 检查自赋值 // 4分&
if(this == &other)&
// (2) 释放原有的内存资源 // 3分&
delete [] m_&
// (3)分配新的内存资源,并复制内容 // 3分&
int length = strlen(other.m_data);&
m_data = new char[length+1]; // 若能加 NULL 判断则更好&
strcpy(m_data, other.m_data);&
// (4)返回本对象的引用 // 3分&
} 已知类String的原型为:&
class&String&
String(const&char&*str&=&NULL);&//&普通构造函数&
String(const&String&&other);&//&拷贝构造函数&
~&String(void);&//&析构函数&
String&&&operate&=(const&String&&other);&//&赋值函数&
char&*m_&//&用于保存字符串&
请编写String的上述4个函数。
标准答案:&
//&String的析构函数&
String::~String(void)&//&3分&
delete&[]&m_&
//&由于m_data是内部数据类型,也可以写成&delete&m_&
//&String的普通构造函数&
String::String(const&char&*str)&//&6分&
if(str==NULL)&
m_data&=&new&char[1];&//&若能加&NULL&判断则更好&
*m_data&=&‘\0’;&
int&length&=&strlen(str);&
m_data&=&new&char[length+1];&//&若能加&NULL&判断则更好&
strcpy(m_data,&str);&
//&拷贝构造函数&
String::String(const&String&&other)&//&3分&
int&length&=&strlen(other.m_data);&
m_data&=&new&char[length+1];&//&若能加&NULL&判断则更好&
strcpy(m_data,&other.m_data);&
//&赋值函数&
String&&&String::operate&=(const&String&&other)&//&13分&
//&(1)&检查自赋值&//&4分&
if(this&==&&other)&
//&(2)&释放原有的内存资源&//&3分&
delete&[]&m_&
//&(3)分配新的内存资源,并复制内容&//&3分&
int&length&=&strlen(other.m_data);&
m_data&=&new&char[length+1];&//&若能加&NULL&判断则更好&
strcpy(m_data,&other.m_data);&
//&(4)返回本对象的引用&//&3分&
(1)如果你只得了几十分,请不要声张,也不要太难过。编程质量差往往是由于不良习惯造成的,与人的智力、能力没有多大关系,还是有药可救的。成绩越差,可以进步的空间就越大,中国不就是在落后中赶超发达资本主义国家吗?只要你能下决心改掉不良的编程习惯,第二次考试就能及格了。
(2)如果你考及格了,表明你的技术基础不错,希望你能虚心学习、不断进步。
(3)如果你考出85分以上的好成绩,你有义务和资格为你所在的团队作“C++/C编程”培训。
看过本文的人也看了:
我要留言技术领域:
取消收藏确定要取消收藏吗?
删除图谱提示你保存在该图谱下的知识内容也会被删除,建议你先将内容移到其他图谱中。你确定要删除知识图谱及其内容吗?
删除节点提示无法删除该知识节点,因该节点下仍保存有相关知识内容!
删除节点提示你确定要删除该知识节点吗?}

我要回帖

更多关于 支持c11标准的编译器 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信