您的位置:首页 > 其它

C编译器剖析_C类型系统_相容类型Compatible Type

2015-03-10 20:28 155 查看
在前文对函数调用进行语义检查时,我们用函数CanAssign()来判断“能否把实参赋值给形参”,在函数CanAssign中,我们又用宏IsCompatiblePtr来判断两个指针是否相容。而如果指针类型T1 *
ptr1和T2 * pt2相容,则意味着类型T1和T2是相容的。UCC编译器中ucl\type.c的函数IsCompatibleType()用于判断类型是否相容,与类型系统相关的数据结构请参见”2.4节 C语言的类型系统”中的各个示意图,而在这一节中,我们要对与IsCompatibleType()相关的几个函数做进一步讨论。我们先举个简单的例子来说明类型相容的概念,以下两个对arr的声明被视为兼容的,其出发点是遇到int
arr[]时,我们只是暂时不知道其数组大小,稍后遇到了int arr[3]时,我们知道其数组大小为3,因此以下对arr的两次声明对应的是内存中的同一个数组对象。需要注意的是,以下对于a的两次声明被视为是冲突的,因为int占4字节,double占8字节,两者内部的数据组织形式也不一样,如果硬要规定把a当double来处理,那程序员在分析以下函数f()时,若没有注意到离得比较远的double a,却发现在f()函数中sizeof(a)竟然是8,那其脑门上不知要浮现出多少个问号。但需要注意的是,进行赋值时,我们可以把整型的b赋值给double型的c,这需要编译器进行隐式的类型转换,但不论如何转型,程序员使用变量名b和c时,C编译器与C程序员的约定仍旧是“b对应内存中的一个int类型的变量,而c对应double类型的变量”。虽然在UCC编译器exprchk.c的个别地方用到了IsCompatible()函数,但这个函数主要还是用在declchk.c中,换言之,我们主要是在处理形如int
arr[]和int arr[3]的声明时,用IsCompatible()函数来检查一下"同名的两个声明其类型是否相容",后续在分析declchk.c时我们会看到这一点。
int arr[]; // 相容 compatible
int arr[3];

int a; // 不相容 incompatible

void f(){
sizeof(a);

}

double a;

int b; double c; //

c = b;

接下来,我们来分析一下IsCompatible()函数的代码,如图4.2.31所示。图4.2.31第3行用于判断ty1和ty2是否指向同一个struct type对象,如果是,则为相同类型的对象,自然就是彼此兼容的;否则还需要进一步判断。



图4.2.31 IsCompatible()
在UCC编译器中,我们可用图4.2.31第3行的条件来判断整型和浮点型,例如对int a和int b而言,由于UCC编译器用同一个struct type对象来表示在源代码出现的所有int类型,所以a和b的类型信息都存放在T(INT)所指向的struct type对象中;但对int c和char d而言,c和d分别对应T(INT)和T(CHAR),它们指向两个不同的struct
type对象,因此,图4.2.31第3行的条件不成立,即int 和char不是相容的类型。再次强调的是,我们可以进行int型的c和char型的d之间的相互赋值,但是由于int 和char是不相容的,所以在同一个作用域中,“同时声明char c和
int c”则是错误的。UCC编译器用以下数组Types来表示整型和浮点型对应的类型对象struct type。

#define T(categ) (Types + categ)
struct type Types[VOID - CHAR + 1];

而const int和int也被视为不相容的类型,因为两者在限定符(const或者volatile)上不一致,图4.2.31第5行的条件用于此目的。而int *和struct Data显然风马牛不相及,一个是指针类别,一个是结构体类别,第13行的if条件用于对此进行判断。对于形如T1 * 和T2 *的类型来说,两者都是指针类别,我们就在第17行递归地判断T1和T2是否兼容。而对于数组类型T1
[m]和T2
来说,两者相容的条件是“T1和T2相容”并且“m和n大小一样,或者两者中有一个为0”,例如int [3]和int [0]是相容的,图4.2.31第18至20行完成这样的判别。对函数类型来说,我们还需要递归地检查一下"两个函数的对应参数和返回值类型的相容性",这通过在第22行调用IsCompatibleFunction()函数来处理。而对于整型等基本类型、结构体和联合体来说,我们通过第24行的条件来检查ty1和ty2是否指向同一个struct
type对象。例如,UCC编译器会为以下结构体struct Data1和struct Data2各建立一个struct type对象,用来存放它们的类型信息,这意味着,即使这两个结构体的所有成员的名称和类型都一样,但struct Data1和struct Data2是不相容的。

struct Data1{

int a;

double b;

};

struct Data2{

int a;

double b;

};

对于枚举类型和int来说,如果要把这两者视为兼容的,则删去图4.2.31第9至12行的注释号//即可。把这两种视为相容类型的理由可以是,枚举类型实际上就是范围受限的整型。按ucc\ ansi.c.txt第” 3.5.2.2Enumeration specifiers”节的说明,这两者是相容的(Eachenumerated type shall be compatible
with an integer type)。但是GCC和Clang视之为不相容类型,会对以下声明进行报错。保留上图4.2.31第9至12行的注释号,则UCC编译器也可以对以下声明进行报错“error:Incompatible with previous definition”。从类型安全的角度来考虑,“视enum和int为不相容类型”是更妥当的,毕竟以下enum Color代表的取值范围实际上是{0,1,2},而int代表的取值范围则为{…, -1, 0,1,2,3,….}。
int a; // compatible in UCC, not in GCC/Clang
enum Color{RED,GREEN,BLUE} a;
现在, 让我们来分析一下用于判断两个函数类型是否相容的IsCompatibleFunction(),其代码如图4.2.32所示。我们主要从以下几个方面来判断,函数的返回值、函数的参数、是否变参、是新式风格的函数还是旧式风格的函数。



图4.2.32 IsCompatibleFunction()
图4.2.32第8行用于判断两个函数的返回值类型是否相容,如果返回值类型相容且两个函数都是旧式风格的函数(不具有原型),我们就从第11行返回。对于新式风格的函数,其参数要成为其接口的一部分,我们要判断一下两者的参数个数是否相同,且参数类型是否相容,第20至25行的for循环用于此目的。当然,如果其中一个是变参函数,而另一个不是变参函数,这实际上会导致两个声明对参数个数的约束是不一样的,例如以下对f的两次声明的是冲突的。图4.2.32第15行的if条件用于对此进行判断。第10至27行用于处理两个函数类型都是新式函数的情况。
int f(int a);
int f(int a, …);
而如果一个函数是新式函数,另一个是旧式函数,为了处理方便,我们希望执行到第33行时,sig1能指向新式函数的参数列表,而让sig2指向旧式函数的参数列表,图4.2.32第28至31行进行必要的互换,从而达到这个目的。图4.2.32第33至55行的代码是根据ucc\ansi.c.txt第” 3.5.4.3 Function declarators”节的语义规则来编写的。对以下两个声明来说,由于变参函数void
f(float a,...)是新式风格函数的声明,对有名的参数a不会进行实参提升的操作,对于无名参数,则会进行实参提升操作;由于f()是旧式风格函数,所有的实参都会进行实参提升的操作。
void f(float a,...);

void f();

如果执行函数调用f(1.0f,2.0f),对于第一个实参1.0f而言,若按声明void f(float a,...)的约束,我们不需要进行实参提升,真正入栈的参数就是float型的1.0f;而按void f()的要求,我们需要进行实参提升,真正入栈的是double型的1.0。这是两个矛盾的要求,所以我们把这样的两个函数类型当作不相容的类型。
对于声明void g(int, float)和g()而言,当我们执行函数调用g(3,4.0f)时,第二个参数4.0f会出现同样的问题。但对声明void h(int,int)和h()来说,当我们执行函数调用h(5,6)时,进行实参提升后,真正入栈的参数仍然是两个int型的参数,这不会出现冲突,我们可以视之为相容类型。图4.2.32第36至43行对这些情况进行了判断。
而对于以下代码来说,按旧式风格的函数h(a,b)的要求,当我们执行函数调用h(1.0,2.0)时,实参提升后的入栈参数仍旧是2个double型的浮点数;但按照void h(int,int)的约束,我们需要先把double实参隐式地强制转型为int型整数后,再把转型后得到的2个int型整数入栈,实际执行的函数调用相当于h((int)1.0,(int)2.0)。因此,h(a,b)double
a,b;和h(int,int)也是冲突的。图4.2.32第47至53行用于对此进行判断。

void h(a,b)double a,b;{

}

void h(int,int);

需要再次强调的是,在实际开发中,我们不应去使用旧式风格的函数声明,应编写新式风格的函数,这样C编译器会对函数参数进行更严格的检查,旧式风格的函数所带来的问题我们在第2章时已举过例子。但对C编译器而言,只好背上这个历史的包袱。
当我们面对如下相容的函数声明时,我们需要对这两个类型进行综合,合成出一个与这两者都相容的函数类型,这相当于“求两个整数的最大公约数”的过程。合成后产生的类型Composite Type就相当于是原先两个类型的最大公约数。

int f(int (*)(), double (*)[3]); //f1
int f(int (*)(char *), double (*)[]); // f2

为了表述方便,我们称第一个f为f1,第二个f为f2。函数f1的第1个参数的类型为int (*)(),这是一个“指向旧式函数类型int()”的指针,而函数f2的第1个参数为int (*)(char *),这是一个“指向新式函数类型 int(char *)”的指针,这两个指针类型是相容的。函数类型int (char *)对参数的要求更为严格,所以它们的“最大公约数”为int
(char *)。同理,f1的第2个参数和f2的第2个参数的“最大公约数”为double(*)[3],即“指向double[3]数组”的指针类型。由此,我们得到f1和f2的合成类型为:

int f(int (*)(char *), double (*)[3]);

用于合成新类型的函数CompositeType()如图4.2.33所示,第2行的断言assert告诉我们,只有对相容的两个类型,我们才能求其合成类型。对于形如const T1 *和const T2 *的两个相容类型,我们递归地去求T1和T2的合成类型,不妨记为T3,由此const T1 *和const T2
*的合成类型为const T3 *,第8和第9行的代码完成了这个工作。而对于形如int [3]和int []的两个数组类型,我们为之合成的类型即为int [3],第10和第11行完成这个工作。而对于函数类型,我们需要在第17行合成返回值类型,然后在第20至27行递归地对各个参数类型依次进行合成。



图4.2.33 CompositeType()
在ucl\type.c中,另有一个用于求两个实数类型的“最小公倍数”的函数CommonRealType。实数包括整型和浮点型。当我们执行以下代码,要进行a+c运算时,我们需要把int型的c隐式地转型为double类型,然后与double型的a进行浮点数的加法。按前面的讨论,int和double型变量所占内存大小不一样,数据组织格式也不一样,是不相容的类型。由于double占8个字节,为了最大限度地保留数据精度,我们很自然地会选择把c隐式地转换为double类型,这其实相当于求int和double型的“最小公倍数”。

double a,b;

int c;

b = a+c;

文件ucc\ansi.c.txt的第” 3.2.1.5 Usual arithmetic conversions”节,规定了用于这些算术类型的转换规则,由此编写的代码如图4.2.34所示。图中第2行的参数ty1和ty2都指向实数类型,包括浮点型和整型。当ty1或ty2有一个为long double,则公共类型应取能保留更大数据的long
double;当ty1或者ty2有一个为double,则公共类型为double;而当ty1或者ty2有一个为float,则公共类型为float。第3至8行完成了这些判断。接下来的第9至29行,则用于处理ty1和ty2都是整型的情况,如char、short、int或者long等。



图4.2.34 CommonRealType()
在UCC编译器中,参与算术运算的整型如果低于int型,例如char或者short,则会被先提升为int型。我们介绍过,在16位和32位机器中,int型实际上反映了数据寄存器的大小,我们总是希望CPU的操作数会尽可能地出现在寄存器中。第10至11行完成了这个类型提升操作。当两个整型的符号位一样,即都为无符号整型或者都是有符号整型,我们就取“要占更大内存空间的类型”为公共类型,第15至16行用于此操作。当两个整型的符号位不一样时,为了处理上的方便,第19至24行进行必要的交换,此后,ty1就对应无符号整型,而ty2则对应有符号整型。第25至29行用于选择占更大内存的类型,如果两者所占内存一样,我们就取无符号整型为公共类型。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: