您的位置:首页 > 编程语言 > C语言/C++

C语言面试宝典题目分析(五)

2014-03-03 18:15 281 查看

15、全局变量可不可以定义在可被多个.C文件包含的头文件中?为什么?

答 、可以,在不同的C文件中以static形式来声明同名全局变量。

可以在不同的C文件中声明同名的全局变量,前提是其中只能有一个C文件中对此变量赋初值,此时连接不会出错。

评注:根据14题,我们知道如果不同的C文件中有同名的全局变量,链接时会冲突;由1题我们又知道static会将默认的外链接搞掉,将被修饰的变量改为内链接,因此不同C文件中的全局变量即使名字相同,也不会形成冲突。

 

16、语句for( ;1 ;)有什么问题?它是什么意思?

答 、和while(1)相同,无限循环。

评注:果断写for(;;)。写1干啥。。

 

17、do……while和while……do有什么区别?

答 、前一个循环一遍再判断,后一个判断以后再循环。

评注:while……do?!!又没见过世面了。。。闹心。

 

18、static 全局变量、局部变量、函数与普通全局变量、局部变量、函数static全局变量与普通的全局变量有什么区别?static局部变量和普通局部变量有什么区别?static函数与普通函数有什么区别?

答 、全局变量(外部变量)的说明之前再冠以static就构成了静态的全局变量。全局变量本身就是静态存储方式, 静态全局变量当然也是静态存储方式。 这两者在存储方式上并无不同。这两者的区别虽在于非静态全局变量的作用域是整个源程序,当一个源程序由多个源文件组成时,非静态的全局变量在各个源文件中都是有效的。 而静态全局变量则限制了其作用域, 即只在定义该变量的源文件内有效, 在同一源程序的其它源文件中不能使用它。由于静态全局变量的作用域局限于一个源文件内,只能为该源文件内的函数公用,因此可以避免在其它源文件中引起错误。

从以上分析可以看出,把局部变量改变为静态变量后是改变了它的存储方式即改变了它的生存期。把全局变量改变为静态变量后是改变了它的作用域, 限制了它的使用范围。

static函数与普通函数作用域不同。仅在本文件。只在当前源文件中使用的函数应该说明为内部函数(static),内部函数应该在当前源文件中说明和定义。对于可在当前源文件以外使用的函数,应该在一个头文件中说明,要使用这些函数的源文件要包含这个头文件

static全局变量与普通的全局变量有什么区别:static全局变量只初使化一次,防止在其他文件单元中被引用;

static局部变量和普通局部变量有什么区别:static局部变量只被初始化一次,下一次依据上一次结果值;

static函数与普通函数有什么区别:static函数在内存中只有一份,普通函数在每个被调用中维持一份拷贝

 

19、程序的内存分配

答:一个由c/C++编译的程序占用的内存分为以下几个部分

1、栈区(stack)—由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。

评注:编译期就能够确定大小。直接定义的数组必须用常量(C语言中const变量都不行)来定义数组大小,因为编译器想知道这个数好心里有个底。

既然说到数组,在Java当中可以写出这样的代码:

                   int[][] arr =
newint[10][10];
                   for(int i = 0; i < 10 ;++i){
                            for(int j = 0 ; j
<10;++j){
                                    
arr[i][j] =i * j;
                            }
           }

实际上,数组的维度可以多到8(我只写了8维),运行的时候截图如下:

 


但是在C#是绝对不可以的。我们看例子:

            int[][] arr =
newint[10][];
            for (int
i = 0; i < 10;++i)
            {
               arr[i] =
newint[10];
               
for (int j = 0; j < 10;++j)
               
{
                   arr[i][j] = i * j;
               
}
        }

这样写是没有问题的,结果如下:



但如果写成类似Java的代码,对不起,newint[10][10]标蓝的10是无效的。

C++也是这样,只不过用C#代码里面的引用换成指针。

这里不禁要感慨一下,有人说Java跟C#没啥区别,实际上那是因为他们对这二者并不了解。我最初还以为Java和C++很像的,但除了他们都有个class之外,语法上面像的地方实在不多。与其说C++和Java像,倒不如说和C#像,因为C#总让我有一种(C++ + Java)/2的错觉。当然啦,只是语法表面上说的。

2、堆区(heap)—一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,呵呵。

评注:分明就是一堆内存……

3、全局区(静态区)(static)—全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后由系统释放。

4、文字常量区—常量字符串就是放在这里的。程序结束后由系统释放。

评注:我们还喜欢一个名词——字面量。对于字面量的处理各语言也有特色,比如“abc”在C或C++当中被认为是char*,Java当中就认为是Charsequence或者就是String,最近才流行的go语言的字面量没有类型(只有被赋值以后,才根据复制目标确定其类型……)

5、程序代码区—存放函数体的二进制代码

 

例子程序

这是一个前辈写的,非常详细

//main.cpp

  inta=0;    //全局初始化区

  char *p1;  //全局未初始化区

  main()

  {

   intb;栈

   char s[]="abc";  //栈

   char*p2;         //栈

   char*p3="123456";   //123456\0在常量区,p3在栈上。

   static int c=0;  //全局(静态)初始化区

   p1 =(char*)malloc(10);

   p2 =(char*)malloc(20);   //分配得来得10和20字节的区域就在堆区。

  strcpy(p1,"123456");   //123456\0放在常量区,编译器可能会将它与p3所向"123456"优化成一个地方。

}

评注:强烈希望注意char[] 和char*的区别。

20、解释堆和栈的区别

答:堆(heap)和栈(stack)的区别

(1)申请方式

stack:由系统自动分配。例如,声明在函数中一个局部变量int b;系统自动在栈中为b开辟空间

heap:需要程序员自己申请,并指明大小,在c中malloc函数

如p1=(char*)malloc(10);

在C++中用new运算符

如p2=(char*)malloc(10);

但是注意p1、p2本身是在栈中的。

评注:总是有人喜欢用sizeof(p1)来问别人,32位的机器里面,指针占4个字节,64位机器指针为8个字节。下面我从网上扒了一段文字更清楚的说明这件事情:

原文:32位机指针为什么是4个字节 64位与32位机的区别

http://blog.163.com/pengfeicui@yeah/blog/static/106403250201010793412165/

int类型比较特殊,具体的字节数同机器字长和编译器有关。如果要保证移植性,尽量用__int16 __int32 __int64吧,或者自己typedef int INT32一下。C、C++标准中只规定了某种类型的最小字节数(防止溢出)。

通常的64位技术是相对32位而言的,这个位数指的是CPU GPRs(General-Purpose register通用寄存器)的数据宽度为64位,而32位的处理器的通用寄存器的数据宽度为32位,64位指令集就是运行64位数据的指令,也就是说一次可以运行64bit的数据。

通用寄存器:可用于传送和暂存数据,也可以参与算术逻辑运算,并保存运算结果。通用寄存器的长度取决于机器字长。

字长:字长是CPU的主要技术指标之一,指的是CPU一次能并行处理的二进制的位数,字长是8的整倍数,通常的PC机的字长为16位,32位,64位。一台16位字长的PC机可以直接处理2^16(65536)之内的数字,对于超过此范围的数字需要分解的方法来处理。32位机比16位机优越的原因之一就在于它在一次操作中能处理的数字大,32位机字长的PC机能直接处理的数字为2^32(40亿),能处理的数字越大,则操作的次数就越少,从而系统的效率就越高。

字长与寻址空间:处理器字长是指处理机能同时处理的位数,处理器的字长越大,则说明它的运算能力越强。

处理的寻址范围:要看处理器的地址总线的位数,而不是它的字长。!!!!!如Intel P4处理器字长为32位,地址总线也是32位。8086的数据总线为16为,地址总线为20位(则可寻址的内存空间为2^20=1MB)。新兴的64位处理器的数据总线为64位,地址总线大部分是32位。再看地址总线与寻址范围的关系,存储单元是以Byte为单位,N根地址总线能够访问2^N个存储单元,于是有32为地址总线可访问2^32个存储单元,即4GB。

所以指针为了正确指示内存中的地址,必须按照地址总线的宽度进行变量的存储,因此虽说64位CPU的数据宽度为64位而其地址总线一般不为64位(能访问的内存空间大的惊人,暂时估计应该还做不到),但是一般能超过32位,因此指针的长度大于4个字节(32位),所以64位机的指针字节为64位即8个字节,而32位机的地址总线一般为4个字节,即支持4GB的内存,则其指针的宽度为4个字节。

 

(2)申请后系统的响应

栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。

堆:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。

评注:这个让我想起了stl里面的空间配置器。sgi里面的次级配置器自己维护这一个缓存空间,这个空间当中也是一系列链表,当容器(比如vector)申请内存时,找到最合适的节点返回,如果缓存空间当中找不到合适的节点,就把大节点拆成小节点返回,剩下的作为小节点保留在缓存空间当中。

(3)申请大小的限制

栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。

堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。

评注:又一次让我们看到,堆真的是一堆啊。不过像这样的对内存经过多次分配释放,将会导致内存碎片,不知道操作系统是如何处理的。看来程序开发必然是要朝着深入系统的方向搞起啊。

(4)申请效率的比较:

栈:由系统自动分配,速度较快。但程序员是无法控制的。

堆:是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便.

另外,在WINDOWS下,最好的方式是用Virtual Alloc分配内存,他不是在堆,也不是在栈,而是直接在进程的地址空间中保留一块内存,虽然用起来最不方便。但是速度快,也最灵活。

评注:呃……话说真的得了解一下windows下面的程序开发呢。下面补充一点儿Virtual Alloc的知识:(百度百科)

VirtualAlloc是一个Windows API函数,它包含在windows系统文件Kernel32.dll中,编程时直接使用就可以了,不需要再下载。
该函数的功能是在调用进程的虚地址空间,预定或者提交一部分页

如果用于内存分配的话,并且分配类型未指定MEM_RESET,则系统将自动设置为0;

 

(5)堆和栈中的存储内容

栈:在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。

当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。

堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容由程序员安排。

(6)存取效率的比较

char s1[]="aaaaaaaaaaaaaaa";

char *s2="bbbbbbbbbbbbbbbbb";

aaaaaaaaaaa是在运行时刻赋值的;

而bbbbbbbbbbb是在编译时就确定的;

但是,在以后的存取中,在栈上的数组比指针所指向的字符串(例如堆)快。

比如:

#include

voidmain()

{

char a=1;

char c[]="1234567890";

char *p="1234567890";

a = c[1];

a = p[1];

return;

}

对应的汇编代码

10:a=c[1];

004010678A4DF1  mov cl,byteptr[ebp-0Fh]

0040106A884DFC  mov byteptr[ebp-4],cl

11:a=p[1];

0040106D8B55EC  mov dx,dwordptr[ebp-14h]

004010708A4201   mov al, byteptr[edx+1]

004010738845FC   mov byteptr[ebp-4],al

第一种在读取时直接就把字符串中的元素读到寄存器cl中,而第二种则要先把指针值读到edx中,在根据edx读取字符,显然慢了。

 

评注:总结一番便是

进栈是客官,入堆乃受罪;-------------栈内存的分配和释放不用我们负责,堆则一切听从程序员吩咐

栈少要限号,堆多生碎片;-------------这个好理解

栈连续易取,堆散乱难得;-------------跟前面有一定的逻辑关系

进栈者天定,入堆者后生。-------------栈在编译时已经确定了存谁,堆只有在运行时才知道存谁
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: