连接有关问题

连接器

在C语言中。一个重要的思想就是分别编译。即若干个源程序能够在不同的时候单独进行编译。然后在恰当的时候整合到一起。

​ 典型的连接器把由编译器或汇编器生成的若干个目标模块,整合成一个被称为加载模块或可运行文件的实体,该实体可以被操作系统直接运行。当中,某些目标模块是直接作为输入提供给连接器的;而另外一些目标模块则是依据连接过程的须要,从包含有类似printf函数的库文件里取得的。

​ 连接器通常把目标模块看成是由一组外部对象组成的。每一个外部对象代表着机器内存中的某个部分,并通过一个外部名称来识别。因此,程序中的每一个函数和每一个外部变量,假设没有被声明为static,就都是一个外部对象。某些C编译器会对静态函数和静态变量的名称做一定改变,将它们也作为外部对象。因为经过了“名称修饰”,所以它们不会与其它源程序文件里的同名函数或同名变量发生命名冲突。

​ 大多数连接器都禁止同一个加载模块中的两个不同外部对象拥有同样的名称。然而。在多个目标模块整合成一个加载模块时,这些目标模块可能就包括了同名的外部对象。连接器的一个重要工作就是处理这类命名冲突。

​ 处理命名冲突的最简单办法就是干脆全然禁止。对于外部对象是函数的情形,这样的做法是正确的。一个程序假设包含两个同名的不同函数。编译器根本就不应该接受。而对于外部对象是变量的情形,问题就变得困难了。不同的连接器对这样的情形有着不同的处理方式。

如今讲讲连接器是怎样工作的?

​ 连接器的输入是一组目标模块和库文件。连接器的输出是一个加载模块。连接器读入目标模块和库文件,同时生成加载模块。对每一个目标模块中的每一个外部对象,连接器都要检查加载模块,看是否已有同名的外部对象。假设没有,连接器就将该外部对象加入到加载模块中;假设有,连接器就要開始处理命名冲突。

​ 除了外部对象之外,目标模块还可能包含了对其它模块中的外部对象的引用。比如:一个调用了函数printf的C程序所生成的目标模块。就包含了一个对函数printf的引用。能够猜測得出,该引用指向的是一个位于某个库文件里的外部对象,在连接器生成加载模块的过程中。它必须同一时候记录这些外部对象的引用。当连接器读入一个目标模块时,它必须解析出这个目标模块中定义的全部外部对象的引用,并作出标记说明这些外部对象不再是没有定义的。

如果C语言实现中提供了lint程序,切记要使用

声明与定义

int a;

如果上述语句出现在所有函数体之外,则其被称为对外部对象a的定义,该语句说明了a是一个外部整型变量,同时为a分配了存储空间,要确保未指定初始值的外部变量被初始化为0

extern int a;

不是对a的定义,extren关键字显式说明了a的存储空间是在程序的其他地方分配的。

包含上述语句的程序,必定在程序的别的某个地方包括语句

int a;

每个外部变量只定义一次。

static修饰符

static修饰符是一个能够减少命名冲突的有用工具

static int a;

static将a的作用于限制在一个源文件中,对于其它源文件,a是不可见的。

如果若干函数需要共享一组外部对象,可以将这些函数放在一个源文件中,将他们需要用到的对象在同一源文件中以static修饰符声明,对于函数也同样适用,如果一个函数仅仅被同一源文件中的其他函数调用,则应该将其以static修饰符声明。

形参、实参、返回值

  • 任何一个函数在第一次被调用之前,都必须进行声明或定义
double square(double x){
    return x*x;
}
int main(){
    printf("%g\n",square(0.3));
    return 0;
}
double square(double x);
int main(){
    printf("%g\n",square(0.3));
    return 0;
}
double square(double x){
    return x*x;
}
  • 如果函数的调用和定义不在同一文件中,则需要在调用该函数的文件中进行声明
double square(double x);
int main(){
    printf("%g\n",square(0.3));
    return 0;
}

形参与实参匹配的规则

  • ANSIC允许程序员在声明时指定函数的参数类型

double square(double)

  • 如果一个函数没有float,short或者char类型的参数,在函数声明中完全可以省略参数类型的声说明,但在函数定义中不能省略参数类型的说明。在这种情况下,就需要依赖调用者能够提供数目正确且类型恰当的实参
    1. float类型的参数会自动转换为double类型
    2. short或char类型会自动转换为int类型。

考虑下面一段程序

int main(){
    double s;
    s=sqrt(2);
    printf("%g\n",s);
    return 0;
}

上述程序不能运行的原因有二

  1. sqrt函数本应接受一个双精度值为实参,却被传递了一个整形参数
  2. sqrt函数的返回类型为双精度类型,但在此并没有这样的声明

更正方式一:

double sqrt(double);
int main(){
    double s;
    s=sqrt(2);
    printf("%g\n",s);
    return 0;
}

更正方式二:

double sqrt();
int main(){
    double s;
    s=sqrt(2.0);
    printf("%g\n",s);
    return 0;
}

更正方式三(最好的):

#include <math.h>
int main(){
    double s;
    s=sqrt(2.0);
    printf("%g\n",s);
    return 0;
}

scanf函数和printf函数在不同情形下可以接受不同类型的参数

#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;
}

理想结果为

0 1 2 3 4

可能出现的错误结果:

0 0 0 0 0 1 2 3 4

原因:

变量c被声明为char类型,而不是int类型,程序中要求scanf读入一个整数,应该传递给函数一个指向整数的指针,实际却传递了一个指向字符的指针,scanf函数将该指针作为指向整数的指针接受,并在指针指向的位置存储一个整数,由于int类型所占空间要大于char类型所占空间,所以c附近的内存将被覆盖。

c附近的内存中存储的内容由编译器决定,当c附近的内存存储整数i的低端部分时,每次读入一个数值到c中时,i的低端部分覆盖为0,而i的高端部分本来就为0,相当于i每次被重置为0。当到达文件结束为止后,scanf不在读入新的数值,i才开始正常递增。

检查外部类型

要保证一个特定名称的所有外部定义在每个目标模块中都有相同的类型。所谓的相同的类型必须是严格意义上的相同。

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

extern char* filename;

数组和指针非常累死你,但毕竟不同。

在这里插入图片描述

在这里插入图片描述

更正方法一:

char filename[] ="/etc/passwd";/*文件一*/
extren char filename[];/*文件二*/

更正方法二:

char*filename="/etc/passwd";/*文件一*/
extern char* filename;/*文件二*/

忽略了声明函数的返回类型,或声明了错误的返回类型。

int main(){
    double s;
    s=sqrt(2);
    printf("%g\n",s);
    return 0;
}

c语言中的规则是:如果一个未声明的标识符后跟一个开阔好,那么它将被视为一个返回整型的函数

因此上述程序完全等同于下面的程序

extern int sqrt();
int main(){
    double s;
    s=sqrt(2);
    printf("%g\n",s);
    return 0;
}

头文件

每个外部对象只在一个地方声明。这个声明的地方一般在一个头文件中,需要用到该外部对象的所有模块都应该包括这个头文件,定义该外部对象的模块也应该包括这个头文件

对于filename的例子

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

extern char filename[];

需要用到外部对象filename的每个c源文件中都应该加上这样一个语句

#include "file.h"

最后我们选择一个C源文件,在其中给出filename的初始值,如文件file.c中

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

将include语句展开

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

文件中对于filename有两处声明,两处声明是一致的,且所有声明中最多只有一个是filename的定义。

头文件file.h中声明了filename 的类型,因此每个包含file.h的模块也就自动的正确声明了filename的类型。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小飞学编程ing

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值