C语言的细枝末梢 [3]

这是『C陷阱与缺陷』(C Traps and Pitfalls, Andrew Koenig著)笔记的第三部分,也是最后部分。第一部分见『C语言的细枝末梢(1)』,第二部分见『C语言的细枝末梢(2)』

20、 getchar()的返回值是int型,如果定义一个char型字符去接收,那么当其与EOF或其他类似整数比较时,就可能发生截断,引起潜在的bug。

21、一个输入操作不能随后直接紧跟一个输出操作,反之亦然。如想同时进行输入和输出操作,必须在fread()和fwrite()之间插上fseek()函数调用。

22、有些库函数的使用不需要包含头文件,视编译环境而定。IDE可能包含了常用关键字和函数,标准ANSI C应该不行。

23、使用宏定义时多考虑考虑++、- -,看看有没有副作用。

24、宏不是类型定义。有如下程序片段:

1
2
3
4
5
#define T1 struct foo *
typedef struct foo *T2;
 
T1 a,b; //(1)
T2 a,b; //(2)

语句(1)被扩展为struct foo *a, b;它定义a为一个指向结构的指针,定义b为一个结构。语句(2)则定义了a,b都为一个指向结构的指针。

25、ANSI标准要求long型整数长度至少32位,而short型和int型整数的长度至少应该为16位。将一个char型字符变量c用(unsigned)c转换成与其等价的无符号整数,这个过程可能会出错,因为c在转换成无符号整数时首先被转换成了int,此时由于编译器的不同对最高位处理的不同而得到非预期结果。正确做法是采用语句(unsigned char)c,这样就无需先转换成int,而是直接转换。

26、移位操作的位数应该大于等于0,而严格小于n(n为移位对象长度)。这样的限制条件可以方便在硬件上高效实现移位运算。

27、除法运算余数的符号与被除数相同。vc++下,-5/2的商为-2,余数为-1。

28、"0123456789"[n]是一个合法的数组,因为"0123456789"是一个字符串常量,它可以用来表示一个字符数组,所以在数组名出现的地方都可以用字符串常量来替换。

--EOF--

C语言的细枝末梢 [2]

这是『C陷阱与缺陷』(C Traps and Pitfalls, Andrew Koenig著)笔记的第二部分,第一部分见『C语言的细枝末梢(1)』

8、C语言中只有一维数组,并且数组容量在编译期就必须确认下来。C语言的数组元素可以是任何类型对象,包括数组,这样就用一维数组仿真多维数组。对于一个数组,我们只能做2件事:1)确定该数组大小。2)获得指向该数组下标为0的元素指针。所有其他操作,都是通过指针进行的,包括数组下标运算。

9、给一个指针加上一个整数,与给它的二进制表示加上同样值的整数,其意义完全不同。如果ip指向一个整数,那么ip+1指向计算机内存中的下一个整数。如果p,q为两个整数指针,那么(int)q-(int)p表示两者内存地址的差,而(int)(p-q)表示p,q之间相差多少个元素,这个减法运算只有当p,q指向同一数组时才有效。

10、如果a为一数组名,i为一整数,那么a+i和i+a的含义是一样的。a[i]和i[a]含义也一样。

11、int (*ap)[31]声明了*ap是一个拥有31个整型元素的数组,因此ap就是一个指向这样的数组的指针。

1
2
3
4
5
6
7
8
9
10
11
12
13
int calendar[12][31];//calendar的元素是一个拥有31个整型元素的数组。
int (*monthp)[31]; //声明monthp是一个指向一个拥有31个整型元素的数组的指针。
//因此可以用monthp来遍历calendar数组。
 
/* 这里monthp=calendar相当于monthp=&calendar[0]。 */
for(monthp = calendar; monthp < &calendar[12]; monthp++)
{
    int *dayp;
    /* 这里dayp=*monthp相当于&(*monthp)[0],也相当于dayp=&calendar[0][0]。*/
    for(dayp = *monthp; dayp < &(*monthp)[31]; dayp++)
    {
        *dayp = 0;
    }

12、C语言无法将一个数组作为函数参数直接传递。如果用数组名作参数,那么它会被转换为指向该数组第一个元素的指针。

1
2
3
4
char hello [] = "hello";
printf("%s", hello);
printf("%s", &hello[0]);
//以上两个printf语句完全等同。

同理,int strlen(char s[])与int strlen(char *s)完全等同。

13、将一个整数转换为一个指针,其结果由具体的C编译器实现决定。唯一的例外是常数0,任何编译器必须保证由0转换而来的指针不等于任何有效指针。即,当将0赋值给一个指针变量时,绝对不要使用该指针所指向的内存中内容。

1
2
if (p == (char*)0) {}//合法。
if (strcmp(p, (char*)0) == 0) {}//不合法。

14、数组buffer长度为N,我们可以引用的范围是0-N-1个元素。还可以引用第N个元素的地址用于赋值和比较,比如if (bufptr == &buffer[N]){}。但是不能引用该地址上的元素,比如这样做就是非法的:buffer[N] = 'a'。

15、对表达式a < b,不同的编译器对a和b的求值顺序可能不同。因此,y[i] = x[i++]可能是一个有bug的语句。函数参数的逗号分隔符并非逗号表达式,f(x,y)中x和y的求值顺序并不明确,而g( (x,y) )的求值顺序是明确的,逗号表达式的求值顺序是从左往右,因此它表示g函数含有一个参数,该参数是一个逗号表达式的结果,因此先x后y,再送给g当参数。 16、连接器(linker)的输入是一组目标模块和库文件,输出是一个载入模块(可执行文件实体)。连接器把目标文件看成是由一组外部对象组成的,每个外部对象代表着极其内存中的某个部分,并通过一个外部名称来识别,因此程序中每个函数和外部变量,只要没有被声明为static,就是一个外部对象。连接器一般禁止同一个载入模块(可执行文件实体)中的两个不同外部对象拥有相同的名称。 17、每个外部变量只能被定义一次。static关键字可以限制变量和函数在一个源文件内。当一个函数仅仅被同一个源文件中的函数调用时,就应该被声明为static。 18、程序员必须保证一个特定名称的所有外部定义在每个目标模块都有相同的类型。比如定义为char filename[] = "/etc/passwd";。那就不能再另一个文件中声明extern char* filename;来引用它。因为二者使用存储空间的方式是不同的。 19、C语言中,如果一个未声明的标识符后跟一个开括号,它将被视为一个返回整型的函数。比如未声明sqrt()而直接调用,那么其功能等同于extern int sqrt(); (to be continue...)

C语言的细枝末梢 [1]

这是一篇关于『C陷阱与缺陷』(C Traps and Pitfalls, Andrew Koenig著)的笔记。发现C语言虽然简单,语法上细枝末梢的东西还是挺多的,特记录下以作备忘。

1、C语言以"贪心法"进行词法分析,编译器从左到右一个一个字符读入,直到读入的字符组成的字符串不再可能组成一个有意义的符号。这意味着:
a+++b 等同于 a++ +b。而a+++++b对于编译器来说是不合法的statement。它不会帮你解析为(a++)+(++b)。语句y=x/*p会因为/*符号被编译器解析为注释的开始,而不会是我们想要的y=x/(*p);表达式。

2、用单引号引起的一个字符实际上代表一个整数,整数值对应于该字符在编译器采用的字符集中的索引值。对于采用ASCII字符集的编译器,'a'与0141(oct)和97(dec)严格一致。用双引号引起的字符串代表的是一个指向无名数组的起始字符的指针。因此,语句

1
printf("hello world");

中printf函数接受了"hello world"数组的首字符指针为参数,接着打印输出。
所以下列语句有误:

1
printf('a');//因为'a'代表一个整数。

3、(*(void(*)())0)();的含义。它是调用地址0上的一个函数。首先将0转换为一个void(*)()型的函数指针,该函数的参数为void,返回值也为void。然后编译器就知道了在地址0处要以void(*)()这样的声明调用函数。不能直接以(*0)()调用,两点原因:1)运算符*必须要一个指针来做操作数。2)编译器要知道待调用的函数声明式是怎样的,否则不知道如何处理堆栈返回值等信息。
用typedef关键字可以更加方便定义,要实现上述的转换只需:

1
2
typedef void (*funcptr)();
(*(funcptr)0();

库函数signal的完整声明如下:

1
2
3
void (*signal(int, void(*)(int)))(int);
//signal接受两个参数,一个int型,一个是一个函数指针型。
//返回值是一个函数指针,该函数指针恰好与参数同型。

通过typedef可以简化成如下:

1
2
typedef void (*HANDLER)(int);
HANDLER signal(int, HANDLER);

4、单目运算符的自右向左结合。所以*p++等价于*(p++),而不是(*p)++。
任何一个逻辑运算符的优先级低于任何一个关系运算符。移位运算符的优先级比算术运算符低,但是比关系运算符要高。
运算符优先级由高到低分别是:([] -> .)、单目、算术、移位、关系、逻辑、条件、赋值。
每个逻辑运算符的优先级都不同,比如&&比||高。

5、如果声明结尾的分号被省略,编译器可能会把声明的类型视作函数的返回值类型。比如:

1
2
3
4
5
6
7
8
struct logrec
{
    int date;
    int time;
}
main()
{
}

编译器会认为main函数返回值为一个logrec结构体。

6、函数调用时如果不带参数列表(即使它为空),那么这样的操作其实不是调用函数,而是计算函数地址,其实它什么也不做。

1
2
3
4
5
//某函数声明如下:
void f(void);
//下列语句是在计算函数f的地址。
f;
printf("%x", f);//打印f的地址。

7、C语言允许数组初始化列表出现多余逗号。比如:

1
int week[] = {0, 1, 2, 3, 4, 5, 6,};

这是基于工整性考虑,方便很多自动化工具处理初始化列表。

(to be continue...)

新浪微博php sdk中$o->getRequestToken()返回值为空的解决方案

从新浪微博下载的官方php sdk,跑在本地服务器没问题,上载到虚拟主机上就出现如题所述的index.php页面里$o->getRequestToken()返回值为空。跟踪getRequestToken函数中

1
$request = $this->oAuthRequest($this->requestTokenURL(), 'GET', $parameters);

发现$request还是为空,继续查看oAuthRequest函数,oAuthRequest函数主要调用一个http()方法,该方法用于向新浪微博的授权中心发一个http请求,如果请求成功,会返回一个oauth_token。新浪微博的php sdk采用curl库抓取网页获得所需的返回值。用echo在http方法的前前后后打印了不少变量值,对比本地服务器的输出信息,很容易将问题定位在

1
$response = curl_exec($ci);

本地执行完这一句$response是有值返回的,而在虚拟主机上是没有值返回的。用var_dump(curl_error($ci))打印curl_exec()的错误信息。提示不能连接host,问题已经明确,只要确保虚拟主机能连接到request的请求地址

1
http://api.t.sina.com.cn/oauth/request_token

那么就能得到oauth_token,从而完成oAuth认证的第一步。

以上是$keys = $o->getRequestToken()返回空值的根本原因。而我去网上找过资料以后反而走了不少弯路。
首先,有人说碰到这个原因是虚拟主机上无法解析地址http://api.t.sina.com.cn/oauth/request_token。于是我将weibooauth.php中所有api.t.sina.com.cn都换成了它的ip地址(通过ping得到)。现在想想,其实这个方法是错误的,虽然ping能得到api.t.sina.com.cn的IP地址,但是却不能保证请求授权的地址就是http://IP地址/oauth/request_token。改后发现仍然调用失败,于是只能看weibooauth.php中的源代码,才有了之前的分析,把问题定位在curl_exec()函数,因为这个函数权限比较大,安全系数低,通常虚拟主机提供商会将它初始化为disable_functions,在服务器端用phpinfo()查了一下,果然curl_exec()在它的禁止函数列表中,于是找到客服,请求能不能放开这个函数,客服态度很好,几分钟就放开了curl_exec()执行权限。事实上,只要到了这一步,如果我当初没把api.t.sina.com.cn改成ip地址,那么所有问题就都解决了。

给出问题的解决方案:
1.确保主机能访问得到http://api.t.sina.com.cn/oauth/request_token。
2.确保主机上有curl_exec()的执行权限。

--EOF--