副标题#e#
根基表明
本节主要探讨C编译器下面两方面的特点所激发的一系列常见的编程问题。
对C文件举办别离编译:
C措施凡是由几个小措施(.c文件)构成,编译器将这几个小措施别离编译,然后通过链接措施将它们组合在一起形成一个方针代码。由于编译器每次只能编译一个文件,因此它不能当即查抄需要几个源文件共同才气发明的错误。
对函数的参数和返回值成立姑且变量 C编译器会对函数的参数成立姑且参数,也大概会对函数的返回值隐含通报一个指针。因为这些姑且变量的隐含性存在,使得在某些环境下,出格是有指针存在的时候,会激发一系列的问题。
C文件中所包括的头文件会和C语言一同编译 C语言中被包括的头文件是和.c文件一起编译的,头文件中的问题会反应到.c文件的编译中。
问题:C文件的别离编译
我有一个数组a界说在f1.c中,可是我想在f2.c中计较它的元素个数,用sizeof可以到达这个目标吗?
谜底与阐明:
谜底是否认的,你没有步伐到达目标,本质原因是sizeof操纵符只是在“编译时(compile time)”起浸染,而C语言的编译单元是每次单个.c文件举办编译(其它语言也都如此)。因此,sizeof可以确定同一个源文件中某个数组的巨细,可是对付界说在另一个源文件中的数组它无能为力了,因为那已经是“运行时(run time)”才气确定的工作了。
一件工作要想做,总会有步伐的,下面提供有三种可选的步伐来办理这个问题:
1)、界说一个全局变量,让它记着数组的巨细,在别的一个.c文件中我们通过会见这个全局变量来获得数组的巨细信息(仿佛有点小题大做得不偿失^_^)。
2)、在某个.h文件顶用宏界说数组的巨细,譬喻#define ARRAY_SIZE 50,然后在两个源文件中都包括这个.h文件,通过直接会见ARRAY_SIZE来获得界说在差异.c文件中的数组的巨细。
3)、配置数组的最后一个元素为非凡值,譬喻0,-1,NULL等,然后我们通过遍历数组来寻找这个非凡的末了元素,从而判定数组的长度(这个步伐效率低,也是笨笨的)。
问题:函数返回值隐含通报指针
下面的代码可以正常事情,可是在措施竣事时会有一个致命错误发生。毕竟是什么原因呢?
struct list
{
char *item;
struct list *next;
}
main (argc, argv)
{
...
}
谜底与阐明:
原因很简朴,稍微留意一点不难发明,在界说布局list的右花括弧后头加一个分号就可以办理这个问题:
struct list
{
char *item;
struct list *next;
};//缺了这个分号可不可!
好了,问题是办理了,但,你知道这个错误毕竟导致了什么致命问题吗?问题不是外貌上那么简朴的,OK,让我们来看看工作背后的真相。
首先看一看下面这段代码:
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)、布局界说的后头必然要加分号,颠末上面我的大段报告,我相信你不会犯沟通的错误
#p#副标题#e#
问题:编译器会给函数的参数隐含制造姑且副本
请问运行下面的Test函数会有什么样的功效?
#p#分页标题#e#
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;
谜底与阐明:
不消盯着int myint = 0;看,这一句赋值应该是C语言中最简朴的语句,问题必定不会出在它身上,那么问题只大概呈此刻someheader.h中,最常见的就是该头文件的最后一行的声明(函数也好,变量也好)没有用分号";"末了,那么编译器会将它和myint变量团结起来思量,自然就会堕落了。
这个问题主要是提醒你,在定位问题时思路要拓宽一点,大概要思量一下所包括的头文件是否有问题。
结论:被包括的头文件是和.c文件一起编译的,头文件中的问题会反应到.c文件编译中去的,切记。