c++ 应输入声明_【C陷阱与缺陷】第4章 连接器

本文详细介绍了C++中的连接器概念,强调了分别编译的重要性以及连接器在处理命名冲突和外部对象时的角色。此外,还讨论了声明与定义的区别,static修饰符在减少命名冲突中的作用,以及形参、实参和返回值的处理。同时提到了头文件在解决命名冲突中的应用。
摘要由CSDN通过智能技术生成

上一章:【C陷阱与缺陷】第3章 语义陷阱

下一章:【C陷阱与缺陷】第5章 库函数

一个c程序可能由多个分别编译的部分组成,连接不同部分的叫做连接器(连接编辑器,载入器)。

编译器一次只处理一个文件,所以不能检测出多源程序文件的错误。

许多连接器是独立于c语言实现的,前面的错误是c语言的错误,连接器也可能没办法检测出错误。

当c语言的实现提供了lint的程序,务必用它用它用它,因为它能够捕获到大量这种错误。


1. 连接器

​ c语言的重要思想是分别编译(Separate Compilation),若干个源程序不同时间单独编译,然后恰当的使用整合。连接器一般于c编译器分离,不可能了解到c语言的诸多细节。编译器责任是把c源程序翻译成对连接器有意义的形式。

​ 典型的连接器把由编译器或者汇编器生成的若干个目标模块,整合成一个载入模块可执行文件的实体,该实体可以被操作系统直接执行。其中目标模块有些事作为输入提供给连接器,有些是根据程序的需要,像printf这些函数,是在库文件中获取。

graph LR
A[C源程序]-->|编译器/汇编器|B[目标模块]
B-->C[连接器]
D[库文件]-->C
C-->E[载入模块/可执行文件]

​ 连接器把目标模块看成一组外部对象(external object)组成。每个外部对象为机器中的内存的某个部分,即外部变量或函数,并通过外部名字来识别。通常包含无static修饰的函数与变量,有static修饰的函数与变量需要经过名称修饰后才成为外部对象。

graph LR
A[外部对象]---B["【无static修饰】 函数"]
A---C["【无static修饰】 外部变量"]
A---|名称修饰|D["静态函数"]
A---|名称修饰|E["静态外部变量"]

​ 连接器禁止外部对象有相同的名字,连接器就是需要处理这类命名冲突。连接器对每个目标模块的每个外部对象,都要检查载入模块中是否存在同名的外部对象,没有,就将目标模块添加到载入模块中;如果有,就开始处理命名冲突。

​ 当目标模块包含其他模块的外部对象的引用,例如printf,连接器需要在生成载入模块的同时记录这些外部对象的引用,当连接器读入一个目标模块时,必须解析出这个目标模块中定义的所有外部对象的引用,并说明这些外部对象不再是未定义


2. 声明与定义

​ 声明语句:

int a;

如果出现在所有函数体外,就是一个外部对象a的定义,说明a是个int类型变量,并分配内存,初值为0(某些编译器可能不是)。如果在函数内部则只是声明。

​ 声明语句:

int a = 7;

在声明a的同时给a赋值,是定义语句。

​ 声明语句:

extern int a;

则不是定义语句,说明了a是一个外部int变量,是对外部变量a的引用,是对外部对象的显示引用。例如time库中的void srand(int)函数在外部变量random_seed中保存了n的一份拷贝。

void srand(int n) {
    extern int random_seed;
    random_seed = n;
}

当程序中包含extern int a;语句时,a必须要在本程序或者其他源文件中定义a。


3. 命名冲突与static修饰符

​ 当一个外部对象使用了static修饰时,该外部对象的作用域就只在本源程序中,其他源程序不能看到该外部对象,减少命名冲突的发生。


4. 形参、实参、返回值

​ 如果一个函数在被定义或者声明前被调用,则它的返回类型默认为int类型。

​ ANSI C标准出来前的C编译器,可能是这样声明和定义函数的:

int isvowel();  //声明
//定义
int isvowel(c)
    char c;
{
    //TODO
}

形参类型要匹配,如例子:

#include <stdio.h>
int main() {
    int i;
    char c;
    for (i = 0; i < 5; i++) {
        scanf("%d", &c);
        printf("%d ", i);
    }
    printf("n");
    return 0;
}

如果编译器没有使用内存对齐,则在scanf语句中,给1字节大小的char类型的c赋值时,写入的是4字节大小的int类型,这会覆盖变量i的低位,使得在没输入EOF或者输入数据太小时,该循环会一直执行下去。输出为

0 0 0 0 0 1 2 3 4

而不是目标的

0 1 2 3 4

5. 检查外部类型

一处源文件中声明n:

extern int n;

另一处源文件中定义n:

long n;

则会出现:

  1. 编译器足够聪明,可以参测出这一冲突。
  2. c语言实现对int类型与long类型的内部表示相同,尤其在32位机器上,巧合下正常使用。
  3. 内存对齐,使得intlong的低位,巧合下正常使用。
  4. 变量n两个实例共享储存空间,对一个赋值,对另一个赋完全不同的值,不能使用。

还有上一章讲的错误声明:

char filename[] = "/etc/passwd";    //代表一片连续的内存空间
extern char *filename;              //一个变量,内容是一篇空间的地址。

是错误的,想要正确使用则需要这样改:

char filename[] = "/etc/passwd";
extern char filename[];

或者:

char *filename = "/etc/passwd";
extern char *filename;

如果使用一个未声明的函数,例如未声明就使用了sqrt函数,则相当于在外部写上:

extern int sqrt();

默认返回int类型。


6. 头文件

解决命名冲突的方法可以使用如下规则:

​ 每个外部对象只在一个地方声明,一般在头文件中声明。需要用到该外部对象的模块都要include该头文件,定义该外部对象的模块也要include改头文件。例如前面的filename例子。

创建一个文件file.h,里面包含声明:

extern char filename[];

需要用到该外部对象的每个c源程序都要加上:

#include "file.h"

最后,选择一个适当的c源程序,给出filename的初始值,假设文件名为file.c,包含:

#include "file.h"
char filename[] = "/etc/passwd";

变量的多次声明是合法的。


上一章:

DeathWatch:【C陷阱与缺陷】第3章 语义陷阱​zhuanlan.zhihu.com
15f8f3d28128bec8912b433f6588fce5.png

下一章:

DeathWatch:【C陷阱与缺陷】第5章 库函数​zhuanlan.zhihu.com
6fea16a8594a988dc695697a8b0d91dd.png
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
研究生考试课程为4门,其中数学、外语、政治为统一命题,而专业基础课则根据不同的专业由招生学校自行命题。国家对初试录取分数有总分要求(如某一年要求4门课总分达到310分),另外还有对每门课的最低分数要求(如总分为100的试卷最低达到40分,总分为150的试卷最低达到65分)。编程统计初试合格的人数,并按总分由高到低的顺序输出合格考生的信息。 基本要求:程序运行时首先要求输入:考生姓名,准考证号,报考专业,是否届生,4门课程(政治、数学、外语、专业基础课)成绩。这些原始数据保存到一个文件中。然后输入:录取的总分要求,各课程的最低分数要求。输出要求:过线考生的姓名,准考证号,报考专业,是否届生,4门课程(政治、数学、外语、专业基础课)成绩及总分,这些信息存放到另一个文件中。 测试数据:程序输入不少于10名考生的信息,其中届生和历届生分别有若干名,并且都有合格和不合格的情况。 实现提示:可定义一个考生类存放有关信息和实现相的操作。分数线数据(总分要求和各门课程的要求)可定义另外的类来存放,但能被考生类及其派生类直接访问。 其它要求:初试合格的考生经过复试才能决定是否录取,复试成绩合格(大于一给定分值)可以录取,否则被淘汰。而录取的顺序假设是按照专业基础课和复试成绩的平均值来确定的(因为这涉及到是计划内还是委培问题)。因此,首先输入初试合格考生的复试成绩及复试的合格线分数,然后按上面要求排序输出并标明被淘汰的学生。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值