这个专题的所有学习笔记来自于对武汉大学计算机学院软件工程专业大三上学期的专业必修课《系统级程序设计》的学习(教材为深入理解计算机系统CSAPP),涉及的编程语言全部为C语言和C++语言。
该博客为第0单元的学习笔记,这一单元的主要内容是对C语言的回顾、一些C语言特性的再认识,内容来自《C专家编程》这本书。
1.ANSI C标准中规定的括号“(”嵌套最多可以有多少层?在32位系统中,ANSI C中规定的long int最大值是多少(以16进制表示)ANSI C标准中规定的函数定义中,最多有多少个参数?
答:32, 0x7fffffff(2147483647),31个参数
根据编译器设计者的思路而发展形成的语言特性:
①数组下标从0而不是1开始
②C语言的基本数据类型直接与底层硬件相对应
③auto关键字显然是摆设
④表达式中的数组名可以看作是指针
⑤float被自动扩展为double
⑥不允许嵌套函数(函数内部包含另一个函数的定义)
⑦register关键字
3.可移植的代码:
★严格遵循标准的:只使用已确定的特性、不突破任何由编译器实现的限制、不产生任何依赖由编译器定义的或未确定的或未定义的特性的输出
★遵循标准的:可以依赖一些某种编译器特有的不可移植的特性。
所以一个程序有可能在一个特定的编译器里是遵循标准的,但在另一个编译器里不遵循标准。
4.每个实参都应该有自己的类型,这样它的值就可以赋值给与它所对应的形参类型的对象(该对象的类型不能含有限定符)。
这就是说参数的传递过程类似于赋值。
所以,除非一个const char**类型的对象可以赋值给一个类型为char**的值,否则肯定会产生一条诊断信息,而要使上述的赋值形式合法,必须满足下列条件之一:
两个操作数都是指向有限定符或无限定符的相容类型的指针,左边指针所指向的类型必须具有右边指针所指向类型的全部限定符。
正是这个条件,使得函数调用中实参char*能够与形参const char*匹配。它之所以合法,是因为在下面的代码中:
char *cp;
const char *ccp;
ccp = cp;
首先,左操作数是一个指向有const限定符的char指针;另外,右操作数是一个指向没有限定符的char指针。而char类型与char类型是相容的,左操作数所指向的类型具有右操作数所指向类型的限定符(实际上为无),再加上自身的限定符(const)。
注意,反过来就不能进行赋值,尝试下面的代码:
/*结果会产生编译警告*/
cp = ccp;
const float *类型并不是一个有限定符的类型——它的类型是“指向一个具有const限定符的float类型的指针”,也就是说const限定符是修饰指针所指向的类型,而不是指针本身。
类似地,const char**也是一个没有限定符的指针类型。它的类型是“指向有const限定符的char类型的指针的指针”。
由于char**和const char**都是没有限定符的指针类型,但它们所指向的类型不一样(前者指向char*,后者指向const char*),因此它们是不相容的。
5.const可以用在数据上,如:
const int limit = 10;
这和其他语言差不多,但当你在等式两边加上指针,就有一定难度了:
const int * limitp = &limit;
int i = 27;
limitp = &i;
这段代码表示limitp是一个指向常量整型的指针。这个指针不能用于修改这个整型数,但是在任何时候,这个指针本身的值却可以改变。这样,它就指向了不同的地址,对它进行解除引用(dereference)操作时会得到一个不同的值!
const和*的组合通常只用于在数组形式的参数中模拟传值调用。它声称“我给你一个指向它的指针,但你不能修改它。”这个约定类似于极为常见的void *的用法,尽管在理论上它可以用于任何情形,但通常被限制于把指针从一种类型转换为另一种类型。
1.fall through现象
也许switch语句最大的缺点是它不会在每个case标签后面的语句执行完毕后自动终止。一旦执行某个case语句,程序将会一次执行后面所有的case,除非遇到breal语句,下述代码:
switch(2){
case 1:printf("case 1\n");
case 2:printf("case 2\n");
case 3:printf("case 3\n");
case 4:printf("case 4\n");
default:printf("default\n");
}
其输出结果将是
case2
case3
case4
default
这称之为fall through,它的意思是:如果case语句后面不加break,就依次执行下去,以满足某些特殊情况的要求。但实际上,这是一个非常不好的特性,因为几乎所有的case都需要以break结尾。
2.a function can’t return a function, so you’ll never see foo()()
a function can’t return an array, so you’ll never see foo()[]
an array can’t hold a function, so you’ll never see foo
a function returning a pointer to a function is allowed: int (* fun())();
a function returning a pointer to an array is allowed: int (* foo())[]
an array holding pointers to functions is allowed: int (*foo[])()
an array can hold other arrays, so you’ll frequently see int foo[][]
3.C语言中存在太多的缺省可见性。
定义C函数时,在缺省情况下函数的名字是全局可见的。可以在函数的名字前加个冗余的extern关键字,也可以不加,效果是一样的。这个函数对于链接到它所在的目标文件的任何东西都是可见的。如果想限制对这个函数的访问,就必须加个static关键字。
function apple() {/*在任何地方均可见*/}
extern function pear() {/*任何地方均可见*/}
static function turnip() {/*在这个文件之外不可见*/}
事实上,几乎没有人有在函数名前添加存储类型说明符的习惯,所以绝大多数函数都是全局可见的。
根据实际经验,这种缺省的全局可见性多次被证明是个错误,这已经盖棺定论。软件对象在大多数情况下应该缺省地采用有限可见性。当程序员需要让它全局可见时,应该采用显式的手段。
4.C语言中许多符号是被“重载”的——在不同的上下文环境里有不同的意义。甚至有些关键字也被重载而具有好几种意义,这也是C语言的范围规则对程序员不那么清晰的主要原因。下面的图展示了C语言中类似的符号是如何具有多种不同意义的。
下面的图展示了C语言运算符优先级存在的问题。