c语言pentest总结

http://blog.sina.com.cn/s/blog_7ebe66420101azfv.html     漫步错

程序一般分为:代码区和数据区

数据区分为:静态存储区和动态存储区

静态存储区:静态区(全局区)、常量区(储存字符串)     动态存储区:堆区、栈区

1. main函数执行完毕后,是否可能会再执行一段代码?

答:是。main函数执行完毕后还可以执行代码,它可以通过atexit()函数来注册回调函数,回调函数的形式是void (*function)(void)。atexit()声明在stdlib.h中。具体可以参考:c语言基础函数——exit()和atexit()


2. 给一个字符串、例如 “ababc”要求返回“ab”,因为“ab”连续重复出现且最长。用C/C++语言写一函数完成该算法,给出复杂度。

答:下面是具体的参考代码:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include <stdio.h>  
  2. #include <string.h>  
  3.   
  4. /* 
  5. 函数表示:返回给定字符串中的最大子字符串长度 
  6. 参数:  src:原始字符串 
  7.         sub:最大子字符串 
  8. 返回值:最大子字符串长度(不包含结尾的\0),因为strlen本来也不包含\0。 
  9. 说明: 
  10. 1. 如果存在多个子字符串的长度相同,返回第一个子字符串 
  11. */  
  12. int GetSubStr(char * src, char * sub) {  
  13.     int length;     //原始字符串的长度  
  14.     int max;        //最大子字符串的长度  
  15.     int tmp_max;            //最大子字符串的长度,临时,比较时使用  
  16.     int index;  
  17.     char *p, *q;            //用来遍历字符串的两个指针  
  18.     char *k;        //指向最终的子字符串的起始位置  
  19.   
  20.     length = strlen(src);  
  21.     printf("length of src is %d\n", strlen(src));  
  22.     max = 0;  
  23.     p = src;  
  24.     k = src;  
  25.     //从第一个字符开始遍历  
  26.     while (*p != '\0') {  
  27.         //与前一个字符比较  
  28.         for (q = p + 1; *q != '\0'; q++) {  
  29.             tmp_max = 0;  
  30.             //如果相等了,表示有可能出现最大连续字符串了,之后就要以p和q为起始,比较两者之后的字符串是否相等  
  31.             if (*p == *q) {  
  32.                 //起始字符相等后,就开始比较两个字符串  
  33.                 for (index = 0; index < q - p; index++) {  
  34.                     if (*(p + index) == *(q + index)) {  
  35.                         tmp_max++;  
  36.                     } else {  
  37.                         if (tmp_max > max) {  
  38.                             max = tmp_max;  
  39.                             k = p;  
  40.                         }  
  41.                         break;  
  42.                     }  
  43.                 }  
  44.             }   
  45.         }  
  46.         p++;  
  47.     }  
  48.     strncpy(sub, k, max);  
  49.     //最后需要手动加上一个\0  
  50.     sub[max] = '\0';  
  51.   
  52.     return max;  
  53. }  
  54.   
  55. void main() {  
  56.     char * src = "I am a abc and he is not abc";  
  57.     char result[100];  
  58.     int max = GetSubStr(src, result);  
  59.   
  60.     printf("result[%d]: %s\n", max, result);  
  61.   
  62.     return 0;  
  63. }  
上述代码返回的结果是" abc“,最前面有一个空格。

说明:上述代码的基本思路是使用两个指针,第一个从头开始遍历,即while (*p != '\0');第二个是从第一个指针之后的指针,从该位置开始找与第一个指针指向的内容具有相等字符串的指针位置,即for (q = p + 1; *q != '\0'; q++)循环。该算法的复杂度是O(n^2)。


3. 完成字符串拷贝可以使用 sprintf、strcpy 及 memcpy 函数,请问这些函数有什么区别,你喜欢使用哪个,为什么?

答:这些函数的区别在于实现功能以及操作对象不同。

1. strcpy函数操作的对象是字符串,完成从源字符串到目的字符串的拷贝功能。

2. sprintf函数操作的对象不限于字符串:虽然目的对象是字符串,但是源对象可以是字符串、也可以是任意基本类型的数据。这个函数主要用来实现(字符串或基本数据类型)向字符串的转换功能。如果源对象是字符串,并且指定 %s 格式符,也可实现字符串拷贝功能。

3. memcpy函数顾名思义就是内存拷贝,实现将一个内存块的内容复制到另一个内存块这一功能。内存块由其首地址以及长度确定。程序中出现的实体对象,不论是什么类型,其最终表现就是在内存中占据一席之地(一个内存区间或块)。因此,memcpy 的操作对象不局限于某一类数据类型,或者说可适用于任意数据类型,只要能给出对象的起始地址和内存长度信息、并且对象具有可操作性即可。鉴于memcpy函数等长拷贝的特点以及数据类型代表的物理意义,memcpy 函数通常限于同种类型数据或对象之间的拷贝,其中当然也包括字符串拷贝以及基本数据类型的拷贝。

对于字符串拷贝来说,用上述三个函数都可以实现,但是其实现的效率和使用的方便程度不同:

• strcpy无疑是最合适的选择:效率高且调用方便。

• sprintf要额外指定格式符并且进行格式转化,麻烦且效率不高。

• memcpy 虽然高效,但是需要额外提供拷贝的内存长度这一参数,易错且使用不便;并且如果长度指定过大的话(最优长度是源字符串长度 + 1),还会带来性能的下降。其实strcpy函数一般是在内部调用memcpy函数或者用汇编直接实现的,以达到高效的目的。因此,使用 memcpy 和 strcpy 拷贝字符串在性能上应该没有什么大的差别。

对于非字符串类型的数据的复制来说,strcpy和snprintf一般就无能为力了,可是对memcpy却没有什么影响。但是,对于基本数据类型来说,尽管可以用memcpy进行拷贝,由于有赋值运算符可以方便且高效地进行同种或兼容类型的数据之间的拷贝,所以这种情况下memcpy几乎不被使用。memcpy 的长处是用来实现(通常是内部实现居多)对结构或者数组的拷贝,其目的是或者高效,或者使用方便,甚或两者兼有。


4. 变量的定义和声明有什么区别?

答:定义包含为变量分配存储空间和指定初始值;而声明仅用于向编译器告知变量的名称和类型。


5. 请写出下面代码在32位平台上的运行结果,并说明 sizeof 的性质。

答:直接看代码,注释部分为结果:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. int main(void)  
  4. {  
  5.     char a[30];  
  6.     char *b = (char *)malloc(20 * sizeof(char));  
  7.     printf("%d\n"sizeof(a));      // 30,a是一个数组,这里应该计算所有的值  
  8.     printf("%d\n"sizeof(b));      // 4,b就是一个char指针  
  9.     printf("%d\n"sizeof(a[3]));       // 1,一个char  
  10.     printf("%d\n"sizeof(b + 3));      // 4,一个char指针  
  11.     printf("%d\n"sizeof(*(b + 4)));   // 1,一个char  
  12.     return 0;  
  13. }  
结果已经有了,sizeof的性质也就很明了了,不写了。

6. 请编写一个C函数,该函数给出一个字节中被置1的位的个数,并请给出该题的至少一个不同解法。

答:下面是具体的代码。两种不同的解法中,第一种依赖的是移位,第二种依赖的是2的除法和取余:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3.   
  4. /* 
  5. 函数说明:返回参数中比特位值是1的总个数 
  6. 函数参数:value:待测试的值 
  7. 返回值  :待测试值中的比特位值是1的总个数 
  8. */  
  9. int BitOneTest1(char value) {  
  10.     int index;  
  11.     int result; //返回结果  
  12.     char tmp;   //存放value及其移位值的临时变量  
  13.   
  14.     result = 0;  
  15.     for (index = 0; index < 8; index++) {  
  16.         tmp = value >> index;  
  17.         result += tmp & 1;  
  18.     }  
  19.   
  20.     return result;  
  21. }  
  22.   
  23. int BitOneTest2(char value) {  
  24.     int result;     //返回结果  
  25.     unsigned char tmp;  //这里需要将value转换成unsigned char,否则当value最高位是1时后面除法和取余操作会使结果发生错误  
  26.   
  27.     result = 0;  
  28.     tmp = (unsigned char)value;  
  29.   
  30.     while (tmp) {  
  31.         if ((tmp % 2) == 1) {  
  32.             result++;  
  33.         }  
  34.         tmp = tmp / 2;  
  35.     }  
  36.   
  37.     return result;  
  38. }  
  39.   
  40. int main(void)  
  41. {  
  42.     char num = 0b11111111;  
  43.     printf("[方法1]bit置为1的个数: %d\n", BitOneTest1(num));  
  44.     printf("[方法2]bit置为1的个数: %d\n", BitOneTest2(num));  
  45.   
  46.     return 0;  
  47. }  

又看到一种方法:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include<stdio.h>  
  2. #define TRUE    1  
  3. #define FALSE   0  
  4.   
  5. int BitOneTest3(char value) {  
  6.     int count = 0;  
  7.     while (value)  
  8.     {  
  9.         count++;  
  10.         value = value & (value - 1);  
  11.     }  
  12.     return count;  
  13. }  
  14.   
  15. int main(void)  
  16. {  
  17.     char num = 0b11110111;  
  18.     printf("[方法3]bit置为1的个数: %d\n", BitOneTest3(num));  
  19.     return 0;  
  20. }  

7. 实现自己的itoa和atoi函数。

答:参见http://blog.csdn.net/jiangwei0512/article/details/50668937


8. 定义 int **a[3][4], 则变量占有的内存空间为多少。

答:这题不确定要考什么东西......要是是确定变量a的大小,那么就是4,因为它就是一个指针。如果是数组的大小,那么是4*3*4=48,它实际上是一个指针的指针的二维数组。


9. 编写一个函数,要求输入年月日时分秒,输出该年月日时分秒的下一秒。如输入2004年12月31日23时59分59秒,则输出2005年1月1日0时0分0秒。

答:参见具体代码:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. void NextTime (  
  2.     int *year,   
  3.     int *month,   
  4.     int *date,   
  5.     int *hour,   
  6.     int *minute,   
  7.     int*second  
  8. )  
  9. {  
  10.     int dayOfMonth[12] = { 31,28,31,30,31,30,31,31,30,31,30,31 };  
  11.     if (*year < 0 || *month < 1 || *month > 12 ||  
  12.         *date < 1 || *date > 31 || *hour < 0 || *hour > 23 ||  
  13.         *minute < 0 || *minute > 59 || *second <0 || *second >60)  
  14.         return;  
  15.   
  16.     //处理闰年  
  17.     if ((*year % 400 == 0) || ((*year % 100 != 0) && (*year % 4 == 0)))  
  18.         dayOfMonth[1] = 29;  
  19.   
  20.     *second = *second + 1;  
  21.     if (*second >= 60)  
  22.     {  
  23.         *second = 0;  
  24.         *minute += 1;  
  25.         if (*minute >= 60)  
  26.         {  
  27.             *minute = 0;  
  28.             *hour += 1;  
  29.             if (*hour >= 24)  
  30.             {  
  31.                 *hour = 0;  
  32.                 *date += 1;  
  33.                 if (*date > dayOfMonth[*month - 1])  
  34.                 {  
  35.                     *date = 1;  
  36.                     *month += 1;  
  37.                     if (*month > 12)  
  38.                     {  
  39.                         *month = 1;   
  40.                         *year += 1;  
  41.                     }  
  42.                 }  
  43.             }  
  44.         }  
  45.     }  
  46.     return;  
  47. }  


10. 写一个函数,判断一个int型的整数是否是2的幂,即是否可以表示成2^X的形式(不可以用循环)。

答:这里指定了不能用循环,那么可以考虑使用递归,下面是具体的代码:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #define TRUE    1  
  2. #define FALSE   0  
  3. int IsBinrayPower(int num) {  
  4.     int tmp = num;  
  5.     //0的时候返回FALSE  
  6.     if (tmp == 0) {  
  7.         return FALSE;  
  8.     } else if (tmp == 1) {  
  9.         // 1 = 2 ^ 0,所以返回TRUE;  
  10.         return TRUE;  
  11.     }  
  12.     else {  
  13.         if (tmp % 2 == 0) {  
  14.             return IsBinrayPower(num / 2);  
  15.         } else {  
  16.             return FALSE;  
  17.         }  
  18.           
  19.     }  
  20. }  

还有一种方法,一句代码就搞定了:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #define TRUE    1  
  2. #define FALSE   0  
  3. int IsBinrayPower(int num) {  
  4.     return (num & (num - 1)) ? FALSE : TRUE;  
  5. }  


11. int (* (*f)(int, int))(int)表示什么含义?

答:f是一个函数指针,它的参数是(int ,int),返回值是一个函数指针(形式是int *(g)(int))。虽然看上去很复杂,但是在实际的应用中确实也有类似的,比如在signal.h中就有这样的定义:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. void    (*signal(intvoid (*)(int)))(int);  
(以上来自OS X系统,其它系统可能略有不同)


12. x=x+1,x+=1,x++,为这三个语句的效率排序。并说明为什么。

答:从网上找到的结果是,x=x+1 < x+1 < x++,因为:

x=x+1的执行过程如下:1. 读取右x的地址; 2. x+1; 3. 读取左x的地址; 4. 将右值传给左边的x(编译器并不认为左右x的地址相同)。

x+=1的执行过程如下:1. 读取右x的地址; 2. x+1; 3. 将得到的值传给x(因为x的地址已经读出)。

x++的执行如下:1. 读取右x的地址; 2. x自增1。

但实际的情况是:VS2015下反汇编的结果如下:

[plain]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1.     x = x + 1;  
  2. 00D81685  mov         eax,dword ptr [x]    
  3. 00D81688  add         eax,1    
  4. 00D8168B  mov         dword ptr [x],eax    
  5.     x += 1;  
  6. 00D8168E  mov         eax,dword ptr [x]    
  7. 00D81691  add         eax,1    
  8. 00D81694  mov         dword ptr [x],eax    
  9.     x++;  
  10. 00D81697  mov         eax,dword ptr [x]    
  11. 00D8169A  add         eax,1    
  12. 00D8169D  mov         dword ptr [x],eax    

实际上根本没有什么区别,编译器会去优化的。


13. 写一算法检测单向链表中是否存在环(whether there is a loop in a link list),要求算法复杂度(Algorithm's complexity是O(n)) 并只使用常数空间(space is O(c))。

答:使用追赶的方法,设定两个指针slow、fast,从头指针开始,每次分别前进1步、2步。如存在环,则两者相遇;如不存在环,fast遇到NULL退出。具体的代码如下:
[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #define TRUE    1  
  2. #define FALSE   0  
  3. typedef struct _Node{  
  4.     int data;  
  5.     struct _Node * next;  
  6. } Node;  
  7. int IsLoop(Node *head)  
  8. {  
  9.     Node *pSlow = head;  
  10.     Node *pFast = head;  
  11.     while (pSlow != NULL && pFast != NULL)  
  12.     {  
  13.         pSlow = pSlow->next;  
  14.         pFast = pFast->next->next;  
  15.         if (pSlow == pFast)  
  16.             return TRUE;  
  17.     }  
  18.     return FALSE;  
  19. }  


14. static全局变量与普通全局变量有什么区别?static局部变量和普通局部变量有什么区别?static函数与普通函数有什么区别?

答:

1. static全局变量和普通的全局变量,在内存中的位置是一样的,区别在于static全局变量只在当前的文件有效,而普通全局变量在所有的文件中都有效。

2. static局部变量和普通局部变量在内存中的存储就不一样了,使得普通全局变量每次都会重新初始化,而static局部变量只会初始化一次,之后就沿用上一次的值。

3. static函数和普通函数的差别是,static函数只在当前文件有效,而普通函数默认是extern的,因此在其它文件也有效。


15. 下面程序的打印结果是什么?

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include<stdio.h>  
  2. main()  
  3. {  
  4.     char *a = "hello";  
  5.     char *b = "hello";  
  6.     if (a == b)  
  7.         printf("YES");  
  8.     else  
  9.         printf("NO");  
  10. }  
答:粗的来看应该是打印NO,但是因为hello是存放在静态存储区的,编译器有可能进行优化,将a和b指向同一个hello,此时a==b。在vs2015上的测试结果也是YES。


16. 求a b c的结果。

答:参见具体的代码,这题主要考察的是一个就近原则,即a+++b实际上是(a++)+b;还有就是后++发生在+=等操作之后:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include<stdio.h>  
  2. void main()  
  3. {  
  4.     int a = 5, b = 7, c;  
  5.     c = a+++b;  
  6.     printf("a = %d, b = %d, c= %d\n", a, b, c); //a = 6, b = 7, c= 12  
  7. }  

17. What does the following function return?
[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include<stdio.h>  
  2. char foo(void)  
  3. {  
  4.     unsigned int a = 6;  
  5.     int b = -20;  
  6.     printf("%d\n", ((a + b) > 6));  
  7.     char c;  
  8.     (a + b > 6) ? (c = 1) : (c = 0);  
  9.     return c;  
  10. }  
  11.   
  12. void main()  
  13. {  
  14.     printf("%d\n", foo());  //1  
  15. }  

答:这里的考察的是一个类型的转换,在《c++ primer》中文版第五版中,P34页有这么一句:“当一个算术表达式中既有无符号数又有int值时,那个int值就会转换成无符号数。”因此就可以看到这里的a+b中,-20会被转换成无符号数,所以a+b > 6成立,因此c=1。


18. 有1,2,....一直到n的无序数组,求排序算法,并且要求时间复杂度为O(n),空间复杂度O(1),使用交换,而且一次只能交换两个数。

答:参见具体代码:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include<stdio.h>  
  2. void main()  
  3. {  
  4.     int a[] = { 9, 3, 4, 5, 6, 8, 2, 7, 1 };  
  5.     //排序后的数组应该是{1,2,3,4,5,6,7,8,9},也就是说元素值和下标是一一对应的,这个就是算法的基础  
  6.   
  7.     int tmp;//临时存放数据  
  8.     int index;  
  9.     int length = sizeof(a) / sizeof(int);//数组长度  
  10.   
  11.     for (index = 0; index < length; ) {  
  12.         tmp = a[a[index] - 1];//a[index]是原值,原值应该放的位置是a[index]-1,这个位置对应的值是a[a[index] - 1]  
  13.         a[a[index] - 1] = a[index];  
  14.         a[index] = tmp;  
  15.         //index++的条件是该位置的值已经正确了  
  16.         if (a[index] == index + 1) {  
  17.             index++;  
  18.         }  
  19.     }  
  20.   
  21.     for (index = 0; index < length; index++) {  
  22.         printf("%d ", a[index]);  
  23.     }  
  24.     printf("\n");  
  25. }  

19. 写一个函数判断计算机的字节存储顺序是升序(little-endian)还是降序(big-endian)。

答:需要记住的是,小端模式下低字节内存放低位数据,即,对于int类型的数据0x12345678,最低地址放的是0x78。这个可以作为编程的依据:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include <stdio.h>  
  2.   
  3. int main(void)  
  4. {  
  5.     unsigned int a = 0x12345678;  
  6.     if (*((unsigned char *)&a) == 0x78) {  
  7.         printf("little end\n");  
  8.     }  
  9.     else {  
  10.         printf("big end");  
  11.     }  
  12.     return 0;  
  13. }  

20. 有个数组a[100]存放了100个数,这100个数取自1-99,且只有两个相同的数,剩下的98个数不同,写一个搜索算法找出相同的那个数的值。(注意空间效率时间效率尽可能要低)。

答:参见第18题,两者非常的相似。如果这里按照18题的代码那样进行排序,并将原来的int a[] = { 9, 3, 4, 5, 6, 8, 2, 7, 1 };更换成int a[] = { 7, 3, 4, 5, 6, 8, 2, 7, 1 };代码将会挂死。原因就是因为两个数相同之后,导致这两个相同的一直再做交换。只要修改一下代码,就可以用到这一题上来。

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include<stdio.h>  
  2. void main()  
  3. {  
  4.     int a[] = { 7, 3, 4, 5, 6, 6, 1, 2, 8 };  
  5.   
  6.     int tmp;//临时存放数据  
  7.     int index;  
  8.     int length = sizeof(a) / sizeof(int);//数组长度  
  9.   
  10.     for (index = 0; index < length; ) {  
  11.         tmp = a[a[index] - 1];//a[index]是原值,原值应该放的位置是a[index]-1,这个位置对应的值是a[a[index] - 1]  
  12.         a[a[index] - 1] = a[index];  
  13.         a[index] = tmp;  
  14.         //index++的条件是该位置的值已经正确了  
  15.         if (a[index] == index + 1) {  
  16.             index++;  
  17.         }  
  18.         //新增代码  
  19.         else if (a[index] == a[a[index] - 1]) {  
  20.             printf("found: %d\n", a[index]);//found 6  
  21.             break;  
  22.         };  
  23.     }  
  24. }  

21. 写出判断ABCD四个表达式的是否正确, 若正确, 写出经过表达式中a的值。int a = 4;

(A)a += (a++);   (B) a += (++a) ;   (C) (a++) += a;   (D) (++a) += (a++);

答:AB,CD错误。具体的值参见代码:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include<stdio.h>  
  2. void main()  
  3. {  
  4.     int a = 4;  
  5.     a += (a++);  
  6.     printf("a = %d\n", a);//9  
  7.   
  8.     a = 4;  
  9.     a += (++a);  
  10.     printf("a = %d\n", a);//10  
  11.   
  12.     a = 4;  
  13. //  (a++) += a;//错误  
  14.     printf("a = %d\n", a);  
  15.   
  16.     a = 4;  
  17. //  (++a) += (a++);//错误  
  18.     printf("a = %d\n", a);  
  19. }  

a++和++a不能作为左值。a+=(a++),这里的a实际上只有一次自增会被计算进去,第二次没有,所以结果是4+5=9。


22. switch中允许的数据类型是?

答:整型常量表达式。重点是整型和常量。


23. 嵌入式系统中经常要用到无限循环,你怎么样用C编写死循环呢?

答:参见具体的代码:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. while (1);  
  2. for (;;);  

24. Typedef 在C语言中频繁用以声明一个已经存在的数据类型的同义字。也可以用预处理器做类似的事。例如,思考一下下面的例子: 

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #define dPS struct s *   
  2. typedef struct s * tPS;  

以上两种情况的意图都是要定义dPS 和 tPS 作为一个指向结构s指针。哪种方法更好呢?(如果有的话)为什么? 

答:typedef好。具体见下面的代码:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include<stdio.h>  
  2. struct s {  
  3.     int a;  
  4.     int b;  
  5. };  
  6. #define dPS struct s *   
  7. typedef struct s * tPS;  
  8. void main()  
  9. {  
  10.     dPS p1, p2;  
  11.     tPS p3, p4;  
  12.     printf("p1: %d, p2: %d, p3: %d, p4: %d\n"sizeof(p1), sizeof(p2), sizeof(p3), sizeof(p4)); //p1: 4, p2: 8, p3: 4, p4: 4  
  13. }  
注意结果中sizeof(p2)=8,原因是对于#define,编译器只是在编译时展开宏,所以得到的结果是struct s *p1, p2。p2实际上就是一个结构体,这个可能不是代码真实需要的。

25. 如何判断一段程序是由C编译程序还是由C++编译程序编译的?

答:使用__cplusplus宏:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include <iostream>  
  2. using std::cout;  
  3. using std::endl;  
  4.   
  5. void main()  
  6. {  
  7. #if __cplusplus  
  8.     cout << "c++" << endl;  
  9. #else  
  10.     cout << "c" << endl;  
  11. #endif  
  12. }  

26. 用宏定义写出swap(x, y)。

答:具体参见代码:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #define SWAP(x, y) (x = (x)+(y), y =(x)-(y), x = (x)-(y))  

27. int(*s[10])(int);表示的是什么。

答:表示的是一个函数指针数组,每一个元素是一个形如int (*f)(int)的函数指针。


28. 关于struct位域的题目。

答:下面是几个例子:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. struct s1 {  
  2.     int i : 1;  //0偏移,占1位  
  3.     int j : 2;  //1位偏移,占2位  
  4.     int a : 9;  //3位偏器,占9位  
  5.     //int b : 33;  
  6. };//补齐int大小,总共4个字节  
  7. int main(void)  
  8. {  
  9.     struct s1 s1 = { 1, 1, 1 };  
  10.     printf("%d\n", *(unsigned char *)(&s1));    //取结构体的第一个字节,得到的值是11,即1011b,证明了元素是紧紧排列的。没有位对齐这么一说。  
  11.   
  12.     return 0;  
  13. }  
网上有几个说法:

1. 位域不能跨字节存储。但是实际上是可以的,如上例中的s1.a,但是确实不能大于原始数据类型的大小,这里就不能大于32。

2. 位域也需要对齐,比如这里的s1.j需要按2位对齐,所以偏移从2位开始。实际上也是错的,同类型位域就是紧挨着的。

另外一个例子:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include<stdio.h>  
  2.   
  3. struct s1 {  
  4.     int i : 8;  //0偏移,占8位  
  5.     int j : 4;  //8位偏移,占4位  
  6.     int a : 3;  //12位偏器,占3位  
  7. };//补齐int大小,总共4个字节  
  8.   
  9. struct s2 {  
  10.     int i : 8;  //0偏移,占8位  
  11.     int j : 4;  //8位偏移,占4位  
  12.     int a : 3;  //12位偏器,占3位  
  13.     double b;   //不同类型还是需要对齐的,4字节偏移,占8字节  
  14. };<span style="font-family: Arial, Helvetica, sans-serif;">//照理说总共就12个字节可以了,但是还需要按照最大元素类型的大小来补齐,所以需要时8字节的倍数,所以总大小是16</span>  
  15.   
  16.   
  17. struct s3 {  
  18.     int i : 8;  //0偏移,占8位  
  19.     int j : 4;  //8位偏移,占4位  
  20.     double b;   //不同类型还是需要对齐的,4字节偏移,占8字节  
  21.     int a : 3;  //不同类型还是需要对齐的,12字节偏移,占3位,再补齐到4个字节  
  22. };//照理说总共就16个字节可以了,但是还需要按照最大元素类型的大小来补齐,所以需要时8字节的倍数,所以总大小是24  
  23.   
  24. struct s4 {  
  25.     int i;  
  26.     double j;  
  27. };//按最大元素大小整数倍,所以总大小是16  
  28.   
  29. int main(void)  
  30. {  
  31.     printf("sizeof(int) = %d\n"sizeof(int));      //4  
  32.     printf("sizeof(double) = %d\n"sizeof(double));    //8  
  33.     printf("sizeof(s1) = %d\n"sizeof(struct s1));     //4  
  34.     printf("sizeof(s2) = %d\n"sizeof(struct s2));     //16  
  35.     printf("sizeof(s3) = %d\n"sizeof(struct s3));     //24  
  36.     printf("sizeof(s4) = %d\n"sizeof(struct s4));     //16  
  37.     return 0;  
  38. }  

29. 下面的代码有什么错误:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. swap(int* p1, int* p2)  
  2. {  
  3.     int *p;  
  4.     *p = *p1;  
  5.     *p1 = *p2;  
  6.     *p2 = *p;  
  7. }  
答:p是一个野指针,不能*p = *p1,有可能踩到不该踩的取区域。


30. 求1000!的未尾有几个0。

答:1-1000中,能够整除5的有200个,能够整除25的有40个,能够整除125的有8个,能够整除625的有1个,总共是249个,所以有249个0。

1. 请编写一个c函数,该函数将一个字符串逆序。

答:具体的代码如下,这里主要使用的就是一个数组元素的位置交换:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include <stdio.h>  
  2.   
  3. /* 
  4. 函数说明:将原始字符串逆序 
  5. 函数参数:src:需要逆序的字符串 
  6. 返回值  :逆序后的字符串 
  7. */  
  8. char * ReverseStr(char *src) {  
  9.     int index;  
  10.     int length; //src的长度  
  11.     char tmp;   //临时存放字符  
  12.     int count;  //存放循环次数  
  13.   
  14.     length = strlen(src);  
  15.     count = length / 2;  
  16.   
  17.     for (index = 0; index < count; index++) {  
  18.         tmp = src[index];  
  19.         src[index] = src[length - 1 - index];  
  20.         src[length - 1 - index] = tmp;  
  21.     }  
  22.   
  23.     return src;  
  24. }  
  25.   
  26. int main(void)  
  27. {  
  28.     char s[] = "abcdefg";  
  29.     printf("%s\n", s);      //打印abcdefg  
  30.     printf("%s\n", ReverseStr(s));  //打印gfedcba  
  31.     return 0;  
  32. }  

上面使用的是数组交换,还可以使用递归的方式:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. /* 
  2. 函数说明:将原始字符串逆序 
  3. 函数参数:src:需要逆序的字符串 
  4.           dest:存放临时的排序数据 
  5. 返回值  :逆序后的字符串 
  6. */  
  7. char * ReverseStr2(char *src, char *dest) {  
  8.     int src_length;  
  9.     int des_length;  
  10.     char tmp;  
  11.   
  12.     src_length = strlen(src);  
  13.     des_length = strlen(dest);  
  14.   
  15.     if (src_length == 0) {  
  16.         return dest;  
  17.     }  
  18.     tmp = src[src_length - 1];  
  19.     src[src_length - 1] = 0;  
  20.     dest[des_length] = tmp;  
  21.     dest[des_length + 1] = '\0';  
  22.     return ReverseStr2(src, dest);  
  23. }  
  24.   
  25. int main(void)  
  26. {  
  27.     char s[] = "abcdefg";  
  28.     char tmp[100] = "";//需要足够大,初始字符串长度为0  
  29.     printf("%s\n", s);//打印abcdefg  
  30.     printf("%s\n", ReverseStr2(s, tmp));//打印gfedcba  
  31.     return 0;  
  32. }  

2. Consider the following code:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include <stdio.h>  
  2. #include <string.h>  
  3. int main(int argc, char *argv[]) {  
  4.     int i = 1;  
  5.     char buf[4];  
  6.     strcpy(buf, "AAAA");  
  7.     printf("%d\n", i);  
  8.     return 0;  
  9. }  
a) When compiled and executed on x86, why does this program usually not output what the programmer intended? 
b) Name several ways in which the security problem that causes this program not to output what the programmer intended can be prevented WITHOUT changing the code.

答:这个题目的考点是栈数据的覆盖。因为"AAAA"实际上占据了5个字节(最后有个'\0'),所以根据变量压栈的方式,以及x86下的小端模式,会导致"/0"覆盖1,得到的结果就变成了0。

但是,实际在windows10下vs2015中,结果并不是像上面说的那样,在debug模式下,程序会报错;在release模式下,程序正常运行。且无论是哪种模式,i的值都还是1。

所以说这个问题还是根据编译器不同来决定的,至于vs2015的编译器到底做了什么,可以通过将代码反汇编来确定。

不过作为一个笔试题,能够回答上面就OK了吧,具体先就不追究了。


3. 打印结果是多少?

答:参见具体的代码。(这个题目作为笔试题目是不是太简单了......有什么玄机?)

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include <stdio.h>  
  2. #include <string.h>  
  3. int main(int argc, char *argv[]) {  
  4.     int w = 1, x = 2, y = 3, z = 4;  
  5.     int m;  
  6.     m = (w < x) ? w : x; //m = 1  
  7.     m = (m < y) ? m : y; //m = 1  
  8.     m = (m < 2) ? m : z; //m = 1  
  9.     printf("m = %d\n", m);  //m = 1     
  10.     return 0;  
  11. }  

4. 说出结果:

答:参见具体代码。这题考察的是对文件的基本操作:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include <stdio.h>  
  2. int main()  
  3. {  
  4.     FILE *fp;  
  5.     int i, a[4] = { 1,2,3,4 }, b;  
  6.     fp = fopen("data.dat""wb");           //只写打开或新建一个二进制文件;只允许写数据  
  7.     for (i = 0; i < 4; i++) {  
  8.         fwrite(&a[i], sizeof(int), 1, fp);  //写入1,2,3,4  
  9.     }  
  10.     fclose(fp);  
  11.     fp = fopen("data.dat""rb");           //读写打开一个二进制文件,允许读写数据  
  12.     fseek(fp, -2L * sizeof(int), SEEK_END);         //从尾部开始找数据,这里指向的是3  
  13.     fread(&b, sizeof(int), 1, fp);          //读取指定的位置,就是前面你说的3  
  14.     fclose(fp);  
  15.     printf("b = %d\n", b);              //所以结果是打印3  
  16.   
  17.     return 0;  
  18. }  

5. 下面代码会输出123吗?123创建在堆上还是栈上呢?123的空间是什么时候释放的?
[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include <stdio.h>  
  2. char * GetStr()  
  3. {  
  4.     char *tmp;  
  5.     tmp = "123";  
  6.     return tmp;  
  7. }  
  8.   
  9. void main()  
  10. {  
  11.     printf("%s", GetStr());  
  12. }  
答:会。这里需要区别一点,“123”并不是像普通变量一样放在栈中的,而是存放在.rdata段中,它在编译阶段就确定了。如果编译出来是一个二进制,你可以在二进制中找到这个字符串。所以说,它并不会因为退出函数而消失,因此就能够打印出来。它的释放需要在程序结束的时候。

6. 字符指针、浮点数指针、以及函数指针这三种类型的变量哪个占用的内存最大?为什么?

答:都是指针,占用的一样大。具体见下面的代码:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include <stdio.h>  
  2. int main(void)  
  3. {  
  4.     char *p1;  
  5.     float *p2;  
  6.     void(*fp)(void);  
  7.     printf("p1: %d, p2: %d, fp: %d\n"sizeof(p1), sizeof(p2), sizeof(fp)); //p1: 4, p2: 4, fp: 4  
  8.     return 0;  
  9. }  


7. char **p, a[16][8];  问:p=a是否会导致程序在以后出现问题?为什么?

答:本身并不会报错,但是还是会存在问题。因为a的实际类型应该是char (*)[8],而不是p的char **。

8. 应用程序在运行时的内存包括代码区和数据区,其中数据区又包括哪些部分?

答:数据区包含堆栈等动态的数据,和全局变量、静态变量和字符串等静态数据,且字符串数据是只读的。


9. 用<<,>>,|,&实现一个WORD(2个字节)的高低位交换。

答:参见具体代码:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include <stdio.h>  
  2. int main(void)  
  3. {  
  4.     unsigned short num = 0xABCD;            //unsigned short在本机上是两个字节的  
  5.     printf("num: 0x%x\n", num);     //num: 0xabcd  
  6.     unsigned char tmp;  
  7.     tmp = num >> 8;               //获取高字节  
  8.     num = (num << 8) | tmp;  
  9.     printf("num: 0x%x\n", num);     //num: 0xcdab  
  10.   
  11.     return 0;  
  12. }  

10. 写出程序运行结果:

答:参见具体代码,这里主要考察的是static在非初次进出函数时并不会重新赋值:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include <stdio.h>  
  2. int sum(int a)  
  3. {  
  4.     auto int c = 0;     //值都是0  
  5.     static int b = 3;   //值分别是3,5,7,9,11  
  6.     c += 1;         //值都是1  
  7.     b += 2;         //值分别是5,7,9,11,13  
  8.     return (a + b + c); //值分别是8,10,12,14,16  
  9. }  
  10. void main()  
  11. {  
  12.     int i;  
  13.     int a = 2;  
  14.     for (i = 0; i < 5; i++){  
  15.         printf("%d\n", sum(a)); //打印结果分别是分别是8,10,12,14,16  
  16.     }  
  17. }  


11. 写出函数func(1)的返回值:

答:参见具体的代码。这里主要考察的是switch的一种错误写法,忘了break:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include <stdio.h>  
  2. int func(int a)  
  3. {  
  4.     int b;  
  5.     switch (a)  
  6.     {  
  7.     case 1:   
  8.         b = 30;  
  9.     case 2:  
  10.         b = 20;  
  11.     case 3:  
  12.         b = 16;  
  13.     default:  
  14.         b = 0;  
  15.     }  
  16.     return b;  
  17. }  
  18.   
  19. void main()  
  20. {  
  21.     printf("%d\n", func(1));        //返回0  
  22. }  

12. 写出程序运行的结果:

答:参见代码。本题考察的是指针的加减法运算:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include <stdio.h>  
  2.   
  3. void main()  
  4. {  
  5.     int a[3];  
  6.     a[0] = 0; a[1] = 1; a[2] = 2;  
  7.     int *p, *q;  
  8.     p = a;          //p指向a[0]  
  9.     q = &a[2];      //q指向a[2]  
  10.   
  11.     printf("%d\n", a[q - p]);   //q - p = 2,所以返回的是a[2] = 2;  
  12.   
  13. }  

13. 某文件中定义的静态全局变量(或称静态外部变量)其作用域是 (  )     A.只限某个函数 B.本文件C.跨文件 D.不限制作用域
答:B. 本文件。

14. #include “filename.h”和#include <filename.h>的区别?

答:对于#include <filename.h>编译器从标准库开始搜索filename.h;对于#include “filename.h”编译器从用户工作路径开始搜索filename.h


15. 头文件的作用是什么。

答:1) 提供接口,而不需要提供实现;2)加强类型安全检查。如果某个接口被实现或被使用时,其方式与头文件中的声明不一致,编译器就会指出错误,这一简单的规则能大大减轻程序员调试、改错的负担。


16. 内存的分配方式的分配方式有几种?

答:一、从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量。

二、在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。

三、从堆上分配,亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。


17. 内联函数在编译时是否做参数类型检查?

答:内联函数要做参数类型检查,   这是内联函数跟宏相比的优势。


18. 三个float:a,b,c,问 (a+b)+c==(b+a)+c,(a+b)+c==(a+c)+b 是否成立。
答:两者都可能不行。在比较float或double时,不能简单地比较。由于计算误差,相等的概率很低。应判断两数之差是否落在区间(-e,e)内。这个e应比浮点数的精度大一个数量级。 


19. int i=(j=4,k=8,l=16,m=32); printf("%d", i); 输出是多少? 

答:本题目考察的是","运算符的使用,i=32。


20. 下面代码中的sizeof的使用是否有问题?

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. void UpperCase(char str[]) // 将 str 中的小写字母转换成大写字母  
  2. {  
  3.     for (size_t i = 0; i<sizeof(str) / sizeof(str[0]); ++i)  
  4.         if ('a' <= str[i] && str[i] <= 'z')  
  5.             str[i] -= ('a' - 'A');  
  6. }  
  7. char str[] = "aBcDe";  
  8. cout << "str字符长度为: " << sizeof(str) / sizeof(str[0]) << endl;  
  9. UpperCase(str);  
  10. cout << str << endl;  

答:函数中的sizeof有问题,因为这里的str是参数,它表示的是char *,而不能表示实际的数组,因此sizeof的值是4,而不是真正数组的大小。

注意,这里代码是c++的,并不是c。,不过不影响题目的意思。


21. 全局变量如int i=5; int*(pf)()=foo; 分别在何时被初始化?

答:全局变量的话,在编译的时候就初始化了。函数指针指向具体的函数,需要在链接的时候确定。


22. 以下代码运行结果是什么?

答:参见具体代码。这里考察的是变量的作用域:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include <stdio.h>  
  2. int main()  
  3. {  
  4.     int a, b, c, abc = 0;  
  5.     a = b = c = 40;  
  6.     if (c)  
  7.     {  
  8.         int abc;  
  9.         abc = a*b + c;  //abc只在if作用范围内  
  10.     }  
  11.     printf("%d,%d\n", abc, c);  //abc还是0,c=40  
  12.     return 0;  
  13. }  

23. 请定义一个宏,比较两个数a、b的大小,不能使用大于、小于、if语句。

答:参见具体的代码:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #define MAX(a,b) (((a)-(b))&(1<<31))?(b):(a)   

24. 如何输出源文件的标题和目前执行行的行数。

答:使用预编译宏:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. printf("%s %d\n", __FILE__, __LINE__);    

25. 全局变量和局部变量有什么区别?是怎么实现的?操作系统和编译器是怎么知道的?

答:一些变量在整个程序中都是可见的,它们称为全局变量。一些变量只能在一个函数中可知,称为局部变量。这就是他们的区别。在任何函数外面定义的变量就是全局变量,在函数内部定义的变量是局部变量,这是它们在程序中的实现过程。操作系统和编译器是根据程序运行的内存区域知道他们的,程序的全局数据放在所分配内存的全局数据区,程序的局部数据放在栈区。


26. 局部变量能够和全局变量重名?

答:可以,但是有覆盖。


27. 如何引用一个定义过的全局变量?

答:可以用引用头文件的方式,也可以用extern关键字。如果用引用头文件方式来引用某个在头文件中声明的全局变理,假定你将那个变量写错了,那么在编译期间会报错,如果你用extern方式引用时,假定你犯了同样的错误,那么在编译期间不会报错,而在链接期间报错。


28. 请写出下列代码的输出内容:

答:参见具体代码说明,这里主要考察符号优先级的内容:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include<stdio.h>  
  2. main()  
  3. {  
  4.     int a, b, c, d;  
  5.     a = 10;  
  6.     b = a++;            //=优先级高,所以先赋值  
  7.     c = ++a;            //++放在前面优先级高,注意有两次++,包括前面那一次,所以c=12  
  8.     d = 10 * a++;                   //120  
  9.     printf("b,c,d:%d,%d,%d\n", b, c, d);    //b,c,d:10,12,120  
  10.     return 0;  
  11. }  


29. 以下代码的运行结果是:

答:参见具体代码:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include<stdio.h>  
  2. #define product(x) ((x)*(x))  
  3. main()  
  4. {  
  5.     int i = 3, j, k;  
  6.     j = product(i++);   //i++ * i++ = 9  
  7.     k = product(++i);   //++i * ++i = 7 * 7 = 49,原来i的值已经是5了,还要再经过两个++,所以是7  
  8.     printf("%d %d\n", j, k);    //9 49  
  9. }  

30. 以下代码的运行结果是:

答:参见具体的代码:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include<stdio.h>  
  2.   
  3. main()  
  4. {  
  5.     unsigned char i = 0xA5;  
  6.     unsigned char j = ~i >> 4 + 1;    //真正的执行过程应该是((~i) >> (4 + 1))  
  7.     printf("j = %d\n", j);  //250  
  8.   
  9. }  
首先执行过程已经写了:(~i)>>(4+1),但是还有另外一个需要注意的是,i的类型是unsigned char,它在~操作中会被提升为int,见具体的反汇编代码:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1.     unsigned char i = 0xA5;  
  2. 0038178E C6 45 FB A5          mov         byte ptr [i],0A5h    
  3.     unsigned char j = ~i >> 4 + 1;    //真正的执行过程应该是((~i) >> (4 + 1))  
  4. 00381792 0F B6 45 FB          movzx       eax,byte ptr [i]    
  5.     unsigned char j = ~i >> 4 + 1;    //真正的执行过程应该是((~i) >> (4 + 1))  
  6. 00381796 F7 D0                not         eax    
  7. 00381798 C1 F8 05             sar         eax,5    
  8. 0038179B 88 45 EF             mov         byte ptr [j],al    
  9.     printf("j = %d\n", j);  
从上述的反汇编代码可以看到,i的值被传到了eax中,得到的结果是0x000000A5,再执行NOT之后变成了0xFFFFFF5A,在右移5位,变成了0xFFFFFFFA,然后再取其中的低8位,得到0xFA,即十进制的250。


31. 以下代码的运行结果:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include<stdio.h>  
  2. main()  
  3. {  
  4.     int arr[] = { 1, 2, 3, 4 };  
  5.     int *ptr = arr;  
  6.     printf("%d %d\n", *ptr, *(++ptr));  
  7. }  
答:vs2015中的打印是2 2。

之前网上看到的答案说“因为参数在函数的传递时是从右到左的,所以会先运行一遍++ptr”。

但是实际上c标准中并没有什么规定,所以其实并不一定会++ptr先运行,真正的答案应该是“不确定”...


32. 以下代码的运行结果:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include<stdio.h>  
  2. main()  
  3. {  
  4.     unsigned int a = 0xFFFFFFF7;  
  5.     unsigned char i = (unsigned char)a;  
  6.     char *b = (char *)&a;  
  7.     printf("%08x, %08x\n", i, *b);  
  8.     printf("%d, %d\n", i, *b);  
  9. }  
答:

000000f7, fffffff7
247, -9

需要注意fffffff7这个结果!


33. 下面代码的打印结果是什么:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include <stdio.h>  
  2.   
  3. typedef struct A  
  4. {  
  5.     int num:2;  
  6. }A;  
  7.   
  8. int main()  
  9. {  
  10.     A a;  
  11.     a.num = 1;  
  12.     a.num++;  
  13.     printf("%d\n", a.num);  
  14.     return 0;  
  15. }  

这个题目有点意思,今天诺基亚面试的时候遇到的。它看似考察的是struct,其实考察的是数的取值范围。

对于最基本的类型char,它有8位,取值范围是-128~127,即-2^7~2^7-1。

而对于A.num,它只有2位,所以对应的取值范围应该是-2^1~2^1-1,即-2~1这四个数。

所以a.num=1的时候再自增的话,就变成了-2。

结果打印就是-2。

http://blog.csdn.net/jiangwei0512/article/details/50673794     

      


1. 请编写一个c函数,该函数将一个字符串逆序。

答:具体的代码如下,这里主要使用的就是一个数组元素的位置交换:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include <stdio.h>  
  2.   
  3. /* 
  4. 函数说明:将原始字符串逆序 
  5. 函数参数:src:需要逆序的字符串 
  6. 返回值  :逆序后的字符串 
  7. */  
  8. char * ReverseStr(char *src) {  
  9.     int index;  
  10.     int length; //src的长度  
  11.     char tmp;   //临时存放字符  
  12.     int count;  //存放循环次数  
  13.   
  14.     length = strlen(src);  
  15.     count = length / 2;  
  16.   
  17.     for (index = 0; index < count; index++) {  
  18.         tmp = src[index];  
  19.         src[index] = src[length - 1 - index];  
  20.         src[length - 1 - index] = tmp;  
  21.     }  
  22.   
  23.     return src;  
  24. }  
  25.   
  26. int main(void)  
  27. {  
  28.     char s[] = "abcdefg";  
  29.     printf("%s\n", s);      //打印abcdefg  
  30.     printf("%s\n", ReverseStr(s));  //打印gfedcba  
  31.     return 0;  
  32. }  

上面使用的是数组交换,还可以使用递归的方式:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. /* 
  2. 函数说明:将原始字符串逆序 
  3. 函数参数:src:需要逆序的字符串 
  4.           dest:存放临时的排序数据 
  5. 返回值  :逆序后的字符串 
  6. */  
  7. char * ReverseStr2(char *src, char *dest) {  
  8.     int src_length;  
  9.     int des_length;  
  10.     char tmp;  
  11.   
  12.     src_length = strlen(src);  
  13.     des_length = strlen(dest);  
  14.   
  15.     if (src_length == 0) {  
  16.         return dest;  
  17.     }  
  18.     tmp = src[src_length - 1];  
  19.     src[src_length - 1] = 0;  
  20.     dest[des_length] = tmp;  
  21.     dest[des_length + 1] = '\0';  
  22.     return ReverseStr2(src, dest);  
  23. }  
  24.   
  25. int main(void)  
  26. {  
  27.     char s[] = "abcdefg";  
  28.     char tmp[100] = "";//需要足够大,初始字符串长度为0  
  29.     printf("%s\n", s);//打印abcdefg  
  30.     printf("%s\n", ReverseStr2(s, tmp));//打印gfedcba  
  31.     return 0;  
  32. }  

2. Consider the following code:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include <stdio.h>  
  2. #include <string.h>  
  3. int main(int argc, char *argv[]) {  
  4.     int i = 1;  
  5.     char buf[4];  
  6.     strcpy(buf, "AAAA");  
  7.     printf("%d\n", i);  
  8.     return 0;  
  9. }  
a) When compiled and executed on x86, why does this program usually not output what the programmer intended? 
b) Name several ways in which the security problem that causes this program not to output what the programmer intended can be prevented WITHOUT changing the code.

答:这个题目的考点是栈数据的覆盖。因为"AAAA"实际上占据了5个字节(最后有个'\0'),所以根据变量压栈的方式,以及x86下的小端模式,会导致"/0"覆盖1,得到的结果就变成了0。

但是,实际在windows10下vs2015中,结果并不是像上面说的那样,在debug模式下,程序会报错;在release模式下,程序正常运行。且无论是哪种模式,i的值都还是1。

所以说这个问题还是根据编译器不同来决定的,至于vs2015的编译器到底做了什么,可以通过将代码反汇编来确定。

不过作为一个笔试题,能够回答上面就OK了吧,具体先就不追究了。


3. 打印结果是多少?

答:参见具体的代码。(这个题目作为笔试题目是不是太简单了......有什么玄机?)

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include <stdio.h>  
  2. #include <string.h>  
  3. int main(int argc, char *argv[]) {  
  4.     int w = 1, x = 2, y = 3, z = 4;  
  5.     int m;  
  6.     m = (w < x) ? w : x; //m = 1  
  7.     m = (m < y) ? m : y; //m = 1  
  8.     m = (m < 2) ? m : z; //m = 1  
  9.     printf("m = %d\n", m);  //m = 1     
  10.     return 0;  
  11. }  

4. 说出结果:

答:参见具体代码。这题考察的是对文件的基本操作:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include <stdio.h>  
  2. int main()  
  3. {  
  4.     FILE *fp;  
  5.     int i, a[4] = { 1,2,3,4 }, b;  
  6.     fp = fopen("data.dat""wb");           //只写打开或新建一个二进制文件;只允许写数据  
  7.     for (i = 0; i < 4; i++) {  
  8.         fwrite(&a[i], sizeof(int), 1, fp);  //写入1,2,3,4  
  9.     }  
  10.     fclose(fp);  
  11.     fp = fopen("data.dat""rb");           //读写打开一个二进制文件,允许读写数据  
  12.     fseek(fp, -2L * sizeof(int), SEEK_END);         //从尾部开始找数据,这里指向的是3  
  13.     fread(&b, sizeof(int), 1, fp);          //读取指定的位置,就是前面你说的3  
  14.     fclose(fp);  
  15.     printf("b = %d\n", b);              //所以结果是打印3  
  16.   
  17.     return 0;  
  18. }  

5. 下面代码会输出123吗?123创建在堆上还是栈上呢?123的空间是什么时候释放的?
[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include <stdio.h>  
  2. char * GetStr()  
  3. {  
  4.     char *tmp;  
  5.     tmp = "123";  
  6.     return tmp;  
  7. }  
  8.   
  9. void main()  
  10. {  
  11.     printf("%s", GetStr());  
  12. }  
答:会。这里需要区别一点,“123”并不是像普通变量一样放在栈中的,而是存放在.rdata段中,它在编译阶段就确定了。如果编译出来是一个二进制,你可以在二进制中找到这个字符串。所以说,它并不会因为退出函数而消失,因此就能够打印出来。它的释放需要在程序结束的时候。

6. 字符指针、浮点数指针、以及函数指针这三种类型的变量哪个占用的内存最大?为什么?

答:都是指针,占用的一样大。具体见下面的代码:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include <stdio.h>  
  2. int main(void)  
  3. {  
  4.     char *p1;  
  5.     float *p2;  
  6.     void(*fp)(void);  
  7.     printf("p1: %d, p2: %d, fp: %d\n"sizeof(p1), sizeof(p2), sizeof(fp)); //p1: 4, p2: 4, fp: 4  
  8.     return 0;  
  9. }  


7. char **p, a[16][8];  问:p=a是否会导致程序在以后出现问题?为什么?

答:本身并不会报错,但是还是会存在问题。因为a的实际类型应该是char (*)[8],而不是p的char **。

8. 应用程序在运行时的内存包括代码区和数据区,其中数据区又包括哪些部分?

答:数据区包含堆栈等动态的数据,和全局变量、静态变量和字符串等静态数据,且字符串数据是只读的。


9. 用<<,>>,|,&实现一个WORD(2个字节)的高低位交换。

答:参见具体代码:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include <stdio.h>  
  2. int main(void)  
  3. {  
  4.     unsigned short num = 0xABCD;            //unsigned short在本机上是两个字节的  
  5.     printf("num: 0x%x\n", num);     //num: 0xabcd  
  6.     unsigned char tmp;  
  7.     tmp = num >> 8;               //获取高字节  
  8.     num = (num << 8) | tmp;  
  9.     printf("num: 0x%x\n", num);     //num: 0xcdab  
  10.   
  11.     return 0;  
  12. }  

10. 写出程序运行结果:

答:参见具体代码,这里主要考察的是static在非初次进出函数时并不会重新赋值:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include <stdio.h>  
  2. int sum(int a)  
  3. {  
  4.     auto int c = 0;     //值都是0  
  5.     static int b = 3;   //值分别是3,5,7,9,11  
  6.     c += 1;         //值都是1  
  7.     b += 2;         //值分别是5,7,9,11,13  
  8.     return (a + b + c); //值分别是8,10,12,14,16  
  9. }  
  10. void main()  
  11. {  
  12.     int i;  
  13.     int a = 2;  
  14.     for (i = 0; i < 5; i++){  
  15.         printf("%d\n", sum(a)); //打印结果分别是分别是8,10,12,14,16  
  16.     }  
  17. }  


11. 写出函数func(1)的返回值:

答:参见具体的代码。这里主要考察的是switch的一种错误写法,忘了break:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include <stdio.h>  
  2. int func(int a)  
  3. {  
  4.     int b;  
  5.     switch (a)  
  6.     {  
  7.     case 1:   
  8.         b = 30;  
  9.     case 2:  
  10.         b = 20;  
  11.     case 3:  
  12.         b = 16;  
  13.     default:  
  14.         b = 0;  
  15.     }  
  16.     return b;  
  17. }  
  18.   
  19. void main()  
  20. {  
  21.     printf("%d\n", func(1));        //返回0  
  22. }  

12. 写出程序运行的结果:

答:参见代码。本题考察的是指针的加减法运算:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include <stdio.h>  
  2.   
  3. void main()  
  4. {  
  5.     int a[3];  
  6.     a[0] = 0; a[1] = 1; a[2] = 2;  
  7.     int *p, *q;  
  8.     p = a;          //p指向a[0]  
  9.     q = &a[2];      //q指向a[2]  
  10.   
  11.     printf("%d\n", a[q - p]);   //q - p = 2,所以返回的是a[2] = 2;  
  12.   
  13. }  

13. 某文件中定义的静态全局变量(或称静态外部变量)其作用域是 (  )     A.只限某个函数 B.本文件C.跨文件 D.不限制作用域
答:B. 本文件。

14. #include “filename.h”和#include <filename.h>的区别?

答:对于#include <filename.h>编译器从标准库开始搜索filename.h;对于#include “filename.h”编译器从用户工作路径开始搜索filename.h


15. 头文件的作用是什么。

答:1) 提供接口,而不需要提供实现;2)加强类型安全检查。如果某个接口被实现或被使用时,其方式与头文件中的声明不一致,编译器就会指出错误,这一简单的规则能大大减轻程序员调试、改错的负担。


16. 内存的分配方式的分配方式有几种?

答:一、从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量。

二、在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。

三、从堆上分配,亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。


17. 内联函数在编译时是否做参数类型检查?

答:内联函数要做参数类型检查,   这是内联函数跟宏相比的优势。


18. 三个float:a,b,c,问 (a+b)+c==(b+a)+c,(a+b)+c==(a+c)+b 是否成立。
答:两者都可能不行。在比较float或double时,不能简单地比较。由于计算误差,相等的概率很低。应判断两数之差是否落在区间(-e,e)内。这个e应比浮点数的精度大一个数量级。 


19. int i=(j=4,k=8,l=16,m=32); printf("%d", i); 输出是多少? 

答:本题目考察的是","运算符的使用,i=32。


20. 下面代码中的sizeof的使用是否有问题?

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. void UpperCase(char str[]) // 将 str 中的小写字母转换成大写字母  
  2. {  
  3.     for (size_t i = 0; i<sizeof(str) / sizeof(str[0]); ++i)  
  4.         if ('a' <= str[i] && str[i] <= 'z')  
  5.             str[i] -= ('a' - 'A');  
  6. }  
  7. char str[] = "aBcDe";  
  8. cout << "str字符长度为: " << sizeof(str) / sizeof(str[0]) << endl;  
  9. UpperCase(str);  
  10. cout << str << endl;  

答:函数中的sizeof有问题,因为这里的str是参数,它表示的是char *,而不能表示实际的数组,因此sizeof的值是4,而不是真正数组的大小。

注意,这里代码是c++的,并不是c。,不过不影响题目的意思。


21. 全局变量如int i=5; int*(pf)()=foo; 分别在何时被初始化?

答:全局变量的话,在编译的时候就初始化了。函数指针指向具体的函数,需要在链接的时候确定。


22. 以下代码运行结果是什么?

答:参见具体代码。这里考察的是变量的作用域:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include <stdio.h>  
  2. int main()  
  3. {  
  4.     int a, b, c, abc = 0;  
  5.     a = b = c = 40;  
  6.     if (c)  
  7.     {  
  8.         int abc;  
  9.         abc = a*b + c;  //abc只在if作用范围内  
  10.     }  
  11.     printf("%d,%d\n", abc, c);  //abc还是0,c=40  
  12.     return 0;  
  13. }  

23. 请定义一个宏,比较两个数a、b的大小,不能使用大于、小于、if语句。

答:参见具体的代码:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #define MAX(a,b) (((a)-(b))&(1<<31))?(b):(a)   

24. 如何输出源文件的标题和目前执行行的行数。

答:使用预编译宏:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. printf("%s %d\n", __FILE__, __LINE__);    

25. 全局变量和局部变量有什么区别?是怎么实现的?操作系统和编译器是怎么知道的?

答:一些变量在整个程序中都是可见的,它们称为全局变量。一些变量只能在一个函数中可知,称为局部变量。这就是他们的区别。在任何函数外面定义的变量就是全局变量,在函数内部定义的变量是局部变量,这是它们在程序中的实现过程。操作系统和编译器是根据程序运行的内存区域知道他们的,程序的全局数据放在所分配内存的全局数据区,程序的局部数据放在栈区。


26. 局部变量能够和全局变量重名?

答:可以,但是有覆盖。


27. 如何引用一个定义过的全局变量?

答:可以用引用头文件的方式,也可以用extern关键字。如果用引用头文件方式来引用某个在头文件中声明的全局变理,假定你将那个变量写错了,那么在编译期间会报错,如果你用extern方式引用时,假定你犯了同样的错误,那么在编译期间不会报错,而在链接期间报错。


28. 请写出下列代码的输出内容:

答:参见具体代码说明,这里主要考察符号优先级的内容:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include<stdio.h>  
  2. main()  
  3. {  
  4.     int a, b, c, d;  
  5.     a = 10;  
  6.     b = a++;            //=优先级高,所以先赋值  
  7.     c = ++a;            //++放在前面优先级高,注意有两次++,包括前面那一次,所以c=12  
  8.     d = 10 * a++;                   //120  
  9.     printf("b,c,d:%d,%d,%d\n", b, c, d);    //b,c,d:10,12,120  
  10.     return 0;  
  11. }  


29. 以下代码的运行结果是:

答:参见具体代码:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include<stdio.h>  
  2. #define product(x) ((x)*(x))  
  3. main()  
  4. {  
  5.     int i = 3, j, k;  
  6.     j = product(i++);   //i++ * i++ = 9  
  7.     k = product(++i);   //++i * ++i = 7 * 7 = 49,原来i的值已经是5了,还要再经过两个++,所以是7  
  8.     printf("%d %d\n", j, k);    //9 49  
  9. }  

30. 以下代码的运行结果是:

答:参见具体的代码:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include<stdio.h>  
  2.   
  3. main()  
  4. {  
  5.     unsigned char i = 0xA5;  
  6.     unsigned char j = ~i >> 4 + 1;    //真正的执行过程应该是((~i) >> (4 + 1))  
  7.     printf("j = %d\n", j);  //250  
  8.   
  9. }  
首先执行过程已经写了:(~i)>>(4+1),但是还有另外一个需要注意的是,i的类型是unsigned char,它在~操作中会被提升为int,见具体的反汇编代码:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1.     unsigned char i = 0xA5;  
  2. 0038178E C6 45 FB A5          mov         byte ptr [i],0A5h    
  3.     unsigned char j = ~i >> 4 + 1;    //真正的执行过程应该是((~i) >> (4 + 1))  
  4. 00381792 0F B6 45 FB          movzx       eax,byte ptr [i]    
  5.     unsigned char j = ~i >> 4 + 1;    //真正的执行过程应该是((~i) >> (4 + 1))  
  6. 00381796 F7 D0                not         eax    
  7. 00381798 C1 F8 05             sar         eax,5    
  8. 0038179B 88 45 EF             mov         byte ptr [j],al    
  9.     printf("j = %d\n", j);  
从上述的反汇编代码可以看到,i的值被传到了eax中,得到的结果是0x000000A5,再执行NOT之后变成了0xFFFFFF5A,在右移5位,变成了0xFFFFFFFA,然后再取其中的低8位,得到0xFA,即十进制的250。


31. 以下代码的运行结果:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include<stdio.h>  
  2. main()  
  3. {  
  4.     int arr[] = { 1, 2, 3, 4 };  
  5.     int *ptr = arr;  
  6.     printf("%d %d\n", *ptr, *(++ptr));  
  7. }  
答:vs2015中的打印是2 2。

之前网上看到的答案说“因为参数在函数的传递时是从右到左的,所以会先运行一遍++ptr”。

但是实际上c标准中并没有什么规定,所以其实并不一定会++ptr先运行,真正的答案应该是“不确定”...


32. 以下代码的运行结果:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include<stdio.h>  
  2. main()  
  3. {  
  4.     unsigned int a = 0xFFFFFFF7;  
  5.     unsigned char i = (unsigned char)a;  
  6.     char *b = (char *)&a;  
  7.     printf("%08x, %08x\n", i, *b);  
  8.     printf("%d, %d\n", i, *b);  
  9. }  
答:

000000f7, fffffff7
247, -9

需要注意fffffff7这个结果!


33. 下面代码的打印结果是什么:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include <stdio.h>  
  2.   
  3. typedef struct A  
  4. {  
  5.     int num:2;  
  6. }A;  
  7.   
  8. int main()  
  9. {  
  10.     A a;  
  11.     a.num = 1;  
  12.     a.num++;  
  13.     printf("%d\n", a.num);  
  14.     return 0;  
  15. }  

这个题目有点意思,今天诺基亚面试的时候遇到的。它看似考察的是struct,其实考察的是数的取值范围。

对于最基本的类型char,它有8位,取值范围是-128~127,即-2^7~2^7-1。

而对于A.num,它只有2位,所以对应的取值范围应该是-2^1~2^1-1,即-2~1这四个数。

所以a.num=1的时候再自增的话,就变成了-2。

结果打印就是-2。

http://blog.csdn.net/jiangwei0512/article/details/50677800

http://blog.csdn.net/jiangwei0512/article/details/50673794

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值