本节主要探讨C编译器下面两方面的特点所引发的一系列常见的编程问题。
对C文件进行分别编译:
C程序通常由几个小程序(.c文件)组成,编译器将这几个小程序分别编译,然后通过链接程序将它们组合在一起形成一个目标代码。由于编译器每次只能编译一个文件,因此它不能立即检查需要几个源文件配合才能发现的错误。
对函数的参数和返回值建立临时变量
C编译器会对函数的参数建立临时参数,也可能会对函数的返回值隐含传递一个指针。因为这些临时变量的隐含性存在,使得在某些情况下,特别是有指针存在的时候,会引发一系列的问题。
C文件中所包含的头文件会和C语言一同编译
C语言中被包含的头文件是和.c文件一起编译的,头文件中的问题会反映到.c文件的编译中。
strUCt list { char *item; struct list *next; } main (argc, argv) { ... } |
struct list { char *item; struct list *next; };//缺了这个分号可不行! |
VOID Func ( struct my_struct stX) { ....... } struct my_struct stY = {...}; Func (stY); |
当调用函数Func的时候,是把结构变量stY的值拷贝一份到调用栈中,从而作为参数传递给函数FUNC的,这个叫做C语言的参数值传递。我相信这个你一定很清楚,那么,你应该知道:如果函数的返回值是结构变量的话,函数应该如何将值返回给调用者呢?且看下面这段代码:
struct my_structFunc (VOID) { ....... } struct my_struct stY = Func(); |
此时函数Func的返回值是一个结构类型的值,这个返回值被放在内存中一个阴暗恐怖的地方,同时安排了一个指针指向这个地方(暂时称为“神秘指针”),而这个指针会由C语言的编译器作为一个隐藏参数传递给函数Func。当函数Func返回时,编译器生成的代码将这个由隐藏指针指向的内存区的值拷贝到返回结构stY中,从而完成将结构变量值返回给调用者。
你明白了上述所讲的东东,那么今天问题的真正原因也就呼之欲出了:
因为struct list {...}的定义后面没有加分号,导致主函数main (argc, argv)被编译器理解为是一个返回值为结构变量的函数,从而期望得到除了argc和argv以外的第三个参数,也就是我们上面提到的那个隐含传入的“神秘指针”。可是,大家知道,这里函数是main函数,main函数的参数是由程序中的启动代码(startup code)提供的。而启动代码当然认为main()天生就应该只得到两个参数,要“神秘指针”,当然没有,如此一来, main()在返回时自作主张地去调用栈中访问它的那个并不存在的第三个参数(即神秘指针),这样导致非法访问,产生致命问题。这才是这个问题的真正根源。
建议:
1)、尽量将结构变量的指针而不是结构本身作为函数参数,否则函数调用时内存拷贝的开销可不小,尤其是对那些调用频繁、结构体大的情况。
2)、结构定义的后面一定要加分号,经过上面我的大段讲述,我相信你不会犯相同的错误 问题:编译器会给函数的参数隐含制造临时副本
请问运行下面的Test函数会有什么样的结果?
void GetMemory2(char **p, int num) { *p = (char *)malloc(num); } void Test(void) { char *str = NULL; GetMemory(&str, 100); strcpy(str, "hello"); printf(str); } |
答案与分析:
这是林锐的《C/C++高质量编程指南》上面的例子,拿来用一下。
这样调用会产生如下两个后果:
1)、能够输出hello
2)、内存泄漏
另一个相关问题:
请问运行Test函数会有什么样的结果?
void GetMemory(char *p) { p = (char *)malloc(100); } void Test(void) { char *str = NULL; GetMemory(str); strcpy(str, "hello world"); printf(str); } |
答案与分析:
后果严重,运行的结果是程序崩溃,通过运行调试我们可以看到,经过GetMemory后,Test函数中的 str仍旧是NULL。可想而知,一调用
strcpy(str, "hello world");
程序必然崩溃了事。
原因分析:
C编译器总是会为函数的每个参数制作临时副本,指针参数p的副本是 _p,编译器使 _p = p。如果函数体内的程序修改了_p的内容,就导致参数p的内容作相应的修改。这就是指针可以用作输出参数的原因。在本例中,_p申请了新的内存,只是把_p所指的内存地址改变了,但是p丝毫未变。所以函数GetMemory并不能输出任何东西,如果想要输出动态内存,请使用指向指针的指针,或者,使用指向引用的指针。
问题:头文件和包含它的.c文件一同编译问
下面的代码非常短小,看起来毫无问题,但编译器会报告一个错误,请问问题可能出现在什么地方?
#include "someheader.h" int myint = 0; |