答:是。main函数执行完毕后还可以执行代码,它可以通过atexit()函数来注册回调函数,回调函数的形式是void (*function)(void)。atexit()声明在stdlib.h中。具体可以参考:c语言基础函数——exit()和atexit()
2. 给一个字符串、例如 “ababc”要求返回“ab”,因为“ab”连续重复出现且最长。用C/C++语言写一函数完成该算法,给出复杂度。
答:下面是具体的参考代码:
- #include <stdio.h>
- #include <string.h>
-
-
-
-
-
-
-
-
-
- int GetSubStr(char * src, char * sub) {
- int length;
- int max;
- int tmp_max;
- int index;
- char *p, *q;
- char *k;
-
- length = strlen(src);
- printf("length of src is %d\n", strlen(src));
- max = 0;
- p = src;
- k = src;
-
- while (*p != '\0') {
-
- for (q = p + 1; *q != '\0'; q++) {
- tmp_max = 0;
-
- if (*p == *q) {
-
- for (index = 0; index < q - p; index++) {
- if (*(p + index) == *(q + index)) {
- tmp_max++;
- } else {
- if (tmp_max > max) {
- max = tmp_max;
- k = p;
- }
- break;
- }
- }
- }
- }
- p++;
- }
- strncpy(sub, k, max);
-
- sub[max] = '\0';
-
- return max;
- }
-
- void main() {
- char * src = "I am a abc and he is not abc";
- char result[100];
- int max = GetSubStr(src, result);
-
- printf("result[%d]: %s\n", max, result);
-
- return 0;
- }
上述代码返回的结果是" 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 的性质。
答:直接看代码,注释部分为结果:
- #include <stdio.h>
- #include <stdlib.h>
- int main(void)
- {
- char a[30];
- char *b = (char *)malloc(20 * sizeof(char));
- printf("%d\n", sizeof(a));
- printf("%d\n", sizeof(b));
- printf("%d\n", sizeof(a[3]));
- printf("%d\n", sizeof(b + 3));
- printf("%d\n", sizeof(*(b + 4)));
- return 0;
- }
结果已经有了,sizeof的性质也就很明了了,不写了。
6. 请编写一个C函数,该函数给出一个字节中被置1的位的个数,并请给出该题的至少一个不同解法。
答:下面是具体的代码。两种不同的解法中,第一种依赖的是移位,第二种依赖的是2的除法和取余:
- #include <stdio.h>
- #include <stdlib.h>
-
-
-
-
-
-
- int BitOneTest1(char value) {
- int index;
- int result;
- char tmp;
-
- result = 0;
- for (index = 0; index < 8; index++) {
- tmp = value >> index;
- result += tmp & 1;
- }
-
- return result;
- }
-
- int BitOneTest2(char value) {
- int result;
- unsigned char tmp;
-
- result = 0;
- tmp = (unsigned char)value;
-
- while (tmp) {
- if ((tmp % 2) == 1) {
- result++;
- }
- tmp = tmp / 2;
- }
-
- return result;
- }
-
- int main(void)
- {
- char num = 0b11111111;
- printf("[方法1]bit置为1的个数: %d\n", BitOneTest1(num));
- printf("[方法2]bit置为1的个数: %d\n", BitOneTest2(num));
-
- return 0;
- }
又看到一种方法:
- #include<stdio.h>
- #define TRUE 1
- #define FALSE 0
-
- int BitOneTest3(char value) {
- int count = 0;
- while (value)
- {
- count++;
- value = value & (value - 1);
- }
- return count;
- }
-
- int main(void)
- {
- char num = 0b11110111;
- printf("[方法3]bit置为1的个数: %d\n", BitOneTest3(num));
- return 0;
- }
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秒。
答:参见具体代码:
- void NextTime (
- int *year,
- int *month,
- int *date,
- int *hour,
- int *minute,
- int*second
- )
- {
- int dayOfMonth[12] = { 31,28,31,30,31,30,31,31,30,31,30,31 };
- if (*year < 0 || *month < 1 || *month > 12 ||
- *date < 1 || *date > 31 || *hour < 0 || *hour > 23 ||
- *minute < 0 || *minute > 59 || *second <0 || *second >60)
- return;
-
-
- if ((*year % 400 == 0) || ((*year % 100 != 0) && (*year % 4 == 0)))
- dayOfMonth[1] = 29;
-
- *second = *second + 1;
- if (*second >= 60)
- {
- *second = 0;
- *minute += 1;
- if (*minute >= 60)
- {
- *minute = 0;
- *hour += 1;
- if (*hour >= 24)
- {
- *hour = 0;
- *date += 1;
- if (*date > dayOfMonth[*month - 1])
- {
- *date = 1;
- *month += 1;
- if (*month > 12)
- {
- *month = 1;
- *year += 1;
- }
- }
- }
- }
- }
- return;
- }
10. 写一个函数,判断一个int型的整数是否是2的幂,即是否可以表示成2^X的形式(不可以用循环)。
答:这里指定了不能用循环,那么可以考虑使用递归,下面是具体的代码:
- #define TRUE 1
- #define FALSE 0
- int IsBinrayPower(int num) {
- int tmp = num;
-
- if (tmp == 0) {
- return FALSE;
- } else if (tmp == 1) {
-
- return TRUE;
- }
- else {
- if (tmp % 2 == 0) {
- return IsBinrayPower(num / 2);
- } else {
- return FALSE;
- }
-
- }
- }
还有一种方法,一句代码就搞定了:
- #define TRUE 1
- #define FALSE 0
- int IsBinrayPower(int num) {
- return (num & (num - 1)) ? FALSE : TRUE;
- }
11. int (* (*f)(int, int))(int)表示什么含义?
答:f是一个函数指针,它的参数是(int ,int),返回值是一个函数指针(形式是int *(g)(int))。虽然看上去很复杂,但是在实际的应用中确实也有类似的,比如在signal.h中就有这样的定义:
- void (*signal(int, void (*)(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下反汇编的结果如下:
- x = x + 1;
- 00D81685 mov eax,dword ptr [x]
- 00D81688 add eax,1
- 00D8168B mov dword ptr [x],eax
- x += 1;
- 00D8168E mov eax,dword ptr [x]
- 00D81691 add eax,1
- 00D81694 mov dword ptr [x],eax
- x++;
- 00D81697 mov eax,dword ptr [x]
- 00D8169A add eax,1
- 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退出。具体的代码如下:
- #define TRUE 1
- #define FALSE 0
- typedef struct _Node{
- int data;
- struct _Node * next;
- } Node;
- int IsLoop(Node *head)
- {
- Node *pSlow = head;
- Node *pFast = head;
- while (pSlow != NULL && pFast != NULL)
- {
- pSlow = pSlow->next;
- pFast = pFast->next->next;
- if (pSlow == pFast)
- return TRUE;
- }
- return FALSE;
- }
14. static全局变量与普通全局变量有什么区别?static局部变量和普通局部变量有什么区别?static函数与普通函数有什么区别?
答:
1. static全局变量和普通的全局变量,在内存中的位置是一样的,区别在于static全局变量只在当前的文件有效,而普通全局变量在所有的文件中都有效。
2. static局部变量和普通局部变量在内存中的存储就不一样了,使得普通全局变量每次都会重新初始化,而static局部变量只会初始化一次,之后就沿用上一次的值。
3. static函数和普通函数的差别是,static函数只在当前文件有效,而普通函数默认是extern的,因此在其它文件也有效。
15. 下面程序的打印结果是什么?
- #include<stdio.h>
- main()
- {
- char *a = "hello";
- char *b = "hello";
- if (a == b)
- printf("YES");
- else
- printf("NO");
- }
答:粗的来看应该是打印NO,但是因为hello是存放在静态存储区的,编译器有可能进行优化,将a和b指向同一个hello,此时a==b。在vs2015上的测试结果也是YES。
16. 求a b c的结果。
答:参见具体的代码,这题主要考察的是一个就近原则,即a+++b实际上是(a++)+b;还有就是后++发生在+=等操作之后:
- #include<stdio.h>
- void main()
- {
- int a = 5, b = 7, c;
- c = a+++b;
- printf("a = %d, b = %d, c= %d\n", a, b, c);
- }
17. What does the following function return?
- #include<stdio.h>
- char foo(void)
- {
- unsigned int a = 6;
- int b = -20;
- printf("%d\n", ((a + b) > 6));
- char c;
- (a + b > 6) ? (c = 1) : (c = 0);
- return c;
- }
-
- void main()
- {
- printf("%d\n", foo());
- }
答:这里的考察的是一个类型的转换,在《c++ primer》中文版第五版中,P34页有这么一句:“当一个算术表达式中既有无符号数又有int值时,那个int值就会转换成无符号数。”因此就可以看到这里的a+b中,-20会被转换成无符号数,所以a+b > 6成立,因此c=1。
18. 有1,2,....一直到n的无序数组,求排序算法,并且要求时间复杂度为O(n),空间复杂度O(1),使用交换,而且一次只能交换两个数。
答:参见具体代码:
- #include<stdio.h>
- void main()
- {
- int a[] = { 9, 3, 4, 5, 6, 8, 2, 7, 1 };
-
-
- int tmp;
- int index;
- int length = sizeof(a) / sizeof(int);
-
- for (index = 0; index < length; ) {
- tmp = a[a[index] - 1];
- a[a[index] - 1] = a[index];
- a[index] = tmp;
-
- if (a[index] == index + 1) {
- index++;
- }
- }
-
- for (index = 0; index < length; index++) {
- printf("%d ", a[index]);
- }
- printf("\n");
- }
19. 写一个函数判断计算机的字节存储顺序是升序(little-endian)还是降序(big-endian)。
答:需要记住的是,小端模式下低字节内存放低位数据,即,对于int类型的数据0x12345678,最低地址放的是0x78。这个可以作为编程的依据:
- #include <stdio.h>
-
- int main(void)
- {
- unsigned int a = 0x12345678;
- if (*((unsigned char *)&a) == 0x78) {
- printf("little end\n");
- }
- else {
- printf("big end");
- }
- return 0;
- }
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 };代码将会挂死。原因就是因为两个数相同之后,导致这两个相同的一直再做交换。只要修改一下代码,就可以用到这一题上来。
- #include<stdio.h>
- void main()
- {
- int a[] = { 7, 3, 4, 5, 6, 6, 1, 2, 8 };
-
- int tmp;
- int index;
- int length = sizeof(a) / sizeof(int);
-
- for (index = 0; index < length; ) {
- tmp = a[a[index] - 1];
- a[a[index] - 1] = a[index];
- a[index] = tmp;
-
- if (a[index] == index + 1) {
- index++;
- }
-
- else if (a[index] == a[a[index] - 1]) {
- printf("found: %d\n", a[index]);
- break;
- };
- }
- }
21. 写出判断ABCD四个表达式的是否正确, 若正确, 写出经过表达式中a的值。int a = 4;
(A)a += (a++); (B) a += (++a) ; (C) (a++) += a; (D) (++a) += (a++);
答:AB,CD错误。具体的值参见代码:
- #include<stdio.h>
- void main()
- {
- int a = 4;
- a += (a++);
- printf("a = %d\n", a);
-
- a = 4;
- a += (++a);
- printf("a = %d\n", a);
-
- a = 4;
-
- printf("a = %d\n", a);
-
- a = 4;
-
- printf("a = %d\n", a);
- }
a++和++a不能作为左值。a+=(a++),这里的a实际上只有一次自增会被计算进去,第二次没有,所以结果是4+5=9。
22. switch中允许的数据类型是?
答:整型常量表达式。重点是整型和常量。
23. 嵌入式系统中经常要用到无限循环,你怎么样用C编写死循环呢?
答:参见具体的代码:
24. Typedef 在C语言中频繁用以声明一个已经存在的数据类型的同义字。也可以用预处理器做类似的事。例如,思考一下下面的例子:
- #define dPS struct s *
- typedef struct s * tPS;
以上两种情况的意图都是要定义dPS 和 tPS 作为一个指向结构s指针。哪种方法更好呢?(如果有的话)为什么?
答:typedef好。具体见下面的代码:
- #include<stdio.h>
- struct s {
- int a;
- int b;
- };
- #define dPS struct s *
- typedef struct s * tPS;
- void main()
- {
- dPS p1, p2;
- tPS p3, p4;
- printf("p1: %d, p2: %d, p3: %d, p4: %d\n", sizeof(p1), sizeof(p2), sizeof(p3), sizeof(p4));
- }
注意结果中sizeof(p2)=8,原因是对于#define,编译器只是在编译时展开宏,所以得到的结果是struct s *p1, p2。p2实际上就是一个结构体,这个可能不是代码真实需要的。
25. 如何判断一段程序是由C编译程序还是由C++编译程序编译的?
答:使用__cplusplus宏:
- #include <iostream>
- using std::cout;
- using std::endl;
-
- void main()
- {
- #if __cplusplus
- cout << "c++" << endl;
- #else
- cout << "c" << endl;
- #endif
- }
26. 用宏定义写出swap(x, y)。
答:具体参见代码:
- #define SWAP(x, y) (x = (x)+(y), y =(x)-(y), x = (x)-(y))
27. int(*s[10])(int);表示的是什么。
答:表示的是一个函数指针数组,每一个元素是一个形如int (*f)(int)的函数指针。
28. 关于struct位域的题目。
答:下面是几个例子:
- struct s1 {
- int i : 1;
- int j : 2;
- int a : 9;
-
- };
- int main(void)
- {
- struct s1 s1 = { 1, 1, 1 };
- printf("%d\n", *(unsigned char *)(&s1));
-
- return 0;
- }
网上有几个说法:
1. 位域不能跨字节存储。但是实际上是可以的,如上例中的s1.a,但是确实不能大于原始数据类型的大小,这里就不能大于32。
2. 位域也需要对齐,比如这里的s1.j需要按2位对齐,所以偏移从2位开始。实际上也是错的,同类型位域就是紧挨着的。
另外一个例子:
- #include<stdio.h>
-
- struct s1 {
- int i : 8;
- int j : 4;
- int a : 3;
- };
-
- struct s2 {
- int i : 8;
- int j : 4;
- int a : 3;
- double b;
- };<span style="font-family: Arial, Helvetica, sans-serif;">
-
-
- struct s3 {
- int i : 8;
- int j : 4;
- double b;
- int a : 3;
- };
-
- struct s4 {
- int i;
- double j;
- };
-
- int main(void)
- {
- printf("sizeof(int) = %d\n", sizeof(int));
- printf("sizeof(double) = %d\n", sizeof(double));
- printf("sizeof(s1) = %d\n", sizeof(struct s1));
- printf("sizeof(s2) = %d\n", sizeof(struct s2));
- printf("sizeof(s3) = %d\n", sizeof(struct s3));
- printf("sizeof(s4) = %d\n", sizeof(struct s4));
- return 0;
- }
29. 下面的代码有什么错误:
- swap(int* p1, int* p2)
- {
- int *p;
- *p = *p1;
- *p1 = *p2;
- *p2 = *p;
- }
答:p是一个野指针,不能*p = *p1,有可能踩到不该踩的取区域。
30. 求1000!的未尾有几个0。
答:1-1000中,能够整除5的有200个,能够整除25的有40个,能够整除125的有8个,能够整除625的有1个,总共是249个,所以有249个0。
1. 请编写一个c函数,该函数将一个字符串逆序。
答:具体的代码如下,这里主要使用的就是一个数组元素的位置交换:
- #include <stdio.h>
-
-
-
-
-
-
- char * ReverseStr(char *src) {
- int index;
- int length;
- char tmp;
- int count;
-
- length = strlen(src);
- count = length / 2;
-
- for (index = 0; index < count; index++) {
- tmp = src[index];
- src[index] = src[length - 1 - index];
- src[length - 1 - index] = tmp;
- }
-
- return src;
- }
-
- int main(void)
- {
- char s[] = "abcdefg";
- printf("%s\n", s);
- printf("%s\n", ReverseStr(s));
- return 0;
- }
上面使用的是数组交换,还可以使用递归的方式:
-
-
-
-
-
-
- char * ReverseStr2(char *src, char *dest) {
- int src_length;
- int des_length;
- char tmp;
-
- src_length = strlen(src);
- des_length = strlen(dest);
-
- if (src_length == 0) {
- return dest;
- }
- tmp = src[src_length - 1];
- src[src_length - 1] = 0;
- dest[des_length] = tmp;
- dest[des_length + 1] = '\0';
- return ReverseStr2(src, dest);
- }
-
- int main(void)
- {
- char s[] = "abcdefg";
- char tmp[100] = "";
- printf("%s\n", s);
- printf("%s\n", ReverseStr2(s, tmp));
- return 0;
- }
2. Consider the following code:
- #include <stdio.h>
- #include <string.h>
- int main(int argc, char *argv[]) {
- int i = 1;
- char buf[4];
- strcpy(buf, "AAAA");
- printf("%d\n", i);
- return 0;
- }
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. 打印结果是多少?
答:参见具体的代码。(这个题目作为笔试题目是不是太简单了......有什么玄机?)
- #include <stdio.h>
- #include <string.h>
- int main(int argc, char *argv[]) {
- int w = 1, x = 2, y = 3, z = 4;
- int m;
- m = (w < x) ? w : x;
- m = (m < y) ? m : y;
- m = (m < 2) ? m : z;
- printf("m = %d\n", m);
- return 0;
- }
4. 说出结果:
答:参见具体代码。这题考察的是对文件的基本操作:
- #include <stdio.h>
- int main()
- {
- FILE *fp;
- int i, a[4] = { 1,2,3,4 }, b;
- fp = fopen("data.dat", "wb");
- for (i = 0; i < 4; i++) {
- fwrite(&a[i], sizeof(int), 1, fp);
- }
- fclose(fp);
- fp = fopen("data.dat", "rb");
- fseek(fp, -2L * sizeof(int), SEEK_END);
- fread(&b, sizeof(int), 1, fp);
- fclose(fp);
- printf("b = %d\n", b);
-
- return 0;
- }
5. 下面代码会输出123吗?123创建在堆上还是栈上呢?123的空间是什么时候释放的?
- #include <stdio.h>
- char * GetStr()
- {
- char *tmp;
- tmp = "123";
- return tmp;
- }
-
- void main()
- {
- printf("%s", GetStr());
- }
答:会。这里需要区别一点,“123”并不是像普通变量一样放在栈中的,而是存放在.rdata段中,它在编译阶段就确定了。如果编译出来是一个二进制,你可以在二进制中找到这个字符串。所以说,它并不会因为退出函数而消失,因此就能够打印出来。它的释放需要在程序结束的时候。
6. 字符指针、浮点数指针、以及函数指针这三种类型的变量哪个占用的内存最大?为什么?
答:都是指针,占用的一样大。具体见下面的代码:
- #include <stdio.h>
- int main(void)
- {
- char *p1;
- float *p2;
- void(*fp)(void);
- printf("p1: %d, p2: %d, fp: %d\n", sizeof(p1), sizeof(p2), sizeof(fp));
- return 0;
- }
7. char **p, a[16][8]; 问:p=a是否会导致程序在以后出现问题?为什么?
答:本身并不会报错,但是还是会存在问题。因为a的实际类型应该是char (*)[8],而不是p的char **。
8. 应用程序在运行时的内存包括代码区和数据区,其中数据区又包括哪些部分?
答:数据区包含堆栈等动态的数据,和全局变量、静态变量和字符串等静态数据,且字符串数据是只读的。
9. 用<<,>>,|,&实现一个WORD(2个字节)的高低位交换。
答:参见具体代码:
- #include <stdio.h>
- int main(void)
- {
- unsigned short num = 0xABCD;
- printf("num: 0x%x\n", num);
- unsigned char tmp;
- tmp = num >> 8;
- num = (num << 8) | tmp;
- printf("num: 0x%x\n", num);
-
- return 0;
- }
10. 写出程序运行结果:
答:参见具体代码,这里主要考察的是static在非初次进出函数时并不会重新赋值:
- #include <stdio.h>
- int sum(int a)
- {
- auto int c = 0;
- static int b = 3;
- c += 1;
- b += 2;
- return (a + b + c);
- }
- void main()
- {
- int i;
- int a = 2;
- for (i = 0; i < 5; i++){
- printf("%d\n", sum(a));
- }
- }
11. 写出函数func(1)的返回值:
答:参见具体的代码。这里主要考察的是switch的一种错误写法,忘了break:
- #include <stdio.h>
- int func(int a)
- {
- int b;
- switch (a)
- {
- case 1:
- b = 30;
- case 2:
- b = 20;
- case 3:
- b = 16;
- default:
- b = 0;
- }
- return b;
- }
-
- void main()
- {
- printf("%d\n", func(1));
- }
12. 写出程序运行的结果:
答:参见代码。本题考察的是指针的加减法运算:
- #include <stdio.h>
-
- void main()
- {
- int a[3];
- a[0] = 0; a[1] = 1; a[2] = 2;
- int *p, *q;
- p = a;
- q = &a[2];
-
- printf("%d\n", a[q - p]);
-
- }
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的使用是否有问题?
- void UpperCase(char str[])
- {
- for (size_t i = 0; i<sizeof(str) / sizeof(str[0]); ++i)
- if ('a' <= str[i] && str[i] <= 'z')
- str[i] -= ('a' - 'A');
- }
- char str[] = "aBcDe";
- cout << "str字符长度为: " << sizeof(str) / sizeof(str[0]) << endl;
- UpperCase(str);
- cout << str << endl;
答:函数中的sizeof有问题,因为这里的str是参数,它表示的是char *,而不能表示实际的数组,因此sizeof的值是4,而不是真正数组的大小。
注意,这里代码是c++的,并不是c。,不过不影响题目的意思。
21. 全局变量如int i=5; int*(pf)()=foo; 分别在何时被初始化?
答:全局变量的话,在编译的时候就初始化了。函数指针指向具体的函数,需要在链接的时候确定。
22. 以下代码运行结果是什么?
答:参见具体代码。这里考察的是变量的作用域:
- #include <stdio.h>
- int main()
- {
- int a, b, c, abc = 0;
- a = b = c = 40;
- if (c)
- {
- int abc;
- abc = a*b + c;
- }
- printf("%d,%d\n", abc, c);
- return 0;
- }
23. 请定义一个宏,比较两个数a、b的大小,不能使用大于、小于、if语句。
答:参见具体的代码:
- #define MAX(a,b) (((a)-(b))&(1<<31))?(b):(a)
24. 如何输出源文件的标题和目前执行行的行数。
答:使用预编译宏:
- printf("%s %d\n", __FILE__, __LINE__);
25. 全局变量和局部变量有什么区别?是怎么实现的?操作系统和编译器是怎么知道的?
答:一些变量在整个程序中都是可见的,它们称为全局变量。一些变量只能在一个函数中可知,称为局部变量。这就是他们的区别。在任何函数外面定义的变量就是全局变量,在函数内部定义的变量是局部变量,这是它们在程序中的实现过程。操作系统和编译器是根据程序运行的内存区域知道他们的,程序的全局数据放在所分配内存的全局数据区,程序的局部数据放在栈区。
26. 局部变量能够和全局变量重名?
答:可以,但是有覆盖。
27. 如何引用一个定义过的全局变量?
答:可以用引用头文件的方式,也可以用extern关键字。如果用引用头文件方式来引用某个在头文件中声明的全局变理,假定你将那个变量写错了,那么在编译期间会报错,如果你用extern方式引用时,假定你犯了同样的错误,那么在编译期间不会报错,而在链接期间报错。
28. 请写出下列代码的输出内容:
答:参见具体代码说明,这里主要考察符号优先级的内容:
- #include<stdio.h>
- main()
- {
- int a, b, c, d;
- a = 10;
- b = a++;
- c = ++a;
- d = 10 * a++;
- printf("b,c,d:%d,%d,%d\n", b, c, d);
- return 0;
- }
29. 以下代码的运行结果是:
答:参见具体代码:
- #include<stdio.h>
- #define product(x) ((x)*(x))
- main()
- {
- int i = 3, j, k;
- j = product(i++);
- k = product(++i);
- printf("%d %d\n", j, k);
- }
30. 以下代码的运行结果是:
答:参见具体的代码:
- #include<stdio.h>
-
- main()
- {
- unsigned char i = 0xA5;
- unsigned char j = ~i >> 4 + 1;
- printf("j = %d\n", j);
-
- }
首先执行过程已经写了:(~i)>>(4+1),但是还有另外一个需要注意的是,i的类型是unsigned char,它在~操作中会被提升为int,见具体的反汇编代码:
- unsigned char i = 0xA5;
- 0038178E C6 45 FB A5 mov byte ptr [i],0A5h
- unsigned char j = ~i >> 4 + 1;
- 00381792 0F B6 45 FB movzx eax,byte ptr [i]
- unsigned char j = ~i >> 4 + 1;
- 00381796 F7 D0 not eax
- 00381798 C1 F8 05 sar eax,5
- 0038179B 88 45 EF mov byte ptr [j],al
- printf("j = %d\n", j);
从上述的反汇编代码可以看到,i的值被传到了eax中,得到的结果是0x000000A5,再执行NOT之后变成了0xFFFFFF5A,在右移5位,变成了0xFFFFFFFA,然后再取其中的低8位,得到0xFA,即十进制的250。
31. 以下代码的运行结果:
- #include<stdio.h>
- main()
- {
- int arr[] = { 1, 2, 3, 4 };
- int *ptr = arr;
- printf("%d %d\n", *ptr, *(++ptr));
- }
答:vs2015中的打印是2 2。
之前网上看到的答案说“因为参数在函数的传递时是从右到左的,所以会先运行一遍++ptr”。
但是实际上c标准中并没有什么规定,所以其实并不一定会++ptr先运行,真正的答案应该是“不确定”...
32. 以下代码的运行结果:
- #include<stdio.h>
- main()
- {
- unsigned int a = 0xFFFFFFF7;
- unsigned char i = (unsigned char)a;
- char *b = (char *)&a;
- printf("%08x, %08x\n", i, *b);
- printf("%d, %d\n", i, *b);
- }
答:
000000f7, fffffff7
247, -9
需要注意fffffff7这个结果!
33. 下面代码的打印结果是什么:
- #include <stdio.h>
-
- typedef struct A
- {
- int num:2;
- }A;
-
- int main()
- {
- A a;
- a.num = 1;
- a.num++;
- printf("%d\n", a.num);
- return 0;
- }
这个题目有点意思,今天诺基亚面试的时候遇到的。它看似考察的是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。
1. 请编写一个c函数,该函数将一个字符串逆序。
答:具体的代码如下,这里主要使用的就是一个数组元素的位置交换:
- #include <stdio.h>
-
-
-
-
-
-
- char * ReverseStr(char *src) {
- int index;
- int length;
- char tmp;
- int count;
-
- length = strlen(src);
- count = length / 2;
-
- for (index = 0; index < count; index++) {
- tmp = src[index];
- src[index] = src[length - 1 - index];
- src[length - 1 - index] = tmp;
- }
-
- return src;
- }
-
- int main(void)
- {
- char s[] = "abcdefg";
- printf("%s\n", s);
- printf("%s\n", ReverseStr(s));
- return 0;
- }
上面使用的是数组交换,还可以使用递归的方式:
-
-
-
-
-
-
- char * ReverseStr2(char *src, char *dest) {
- int src_length;
- int des_length;
- char tmp;
-
- src_length = strlen(src);
- des_length = strlen(dest);
-
- if (src_length == 0) {
- return dest;
- }
- tmp = src[src_length - 1];
- src[src_length - 1] = 0;
- dest[des_length] = tmp;
- dest[des_length + 1] = '\0';
- return ReverseStr2(src, dest);
- }
-
- int main(void)
- {
- char s[] = "abcdefg";
- char tmp[100] = "";
- printf("%s\n", s);
- printf("%s\n", ReverseStr2(s, tmp));
- return 0;
- }
2. Consider the following code:
- #include <stdio.h>
- #include <string.h>
- int main(int argc, char *argv[]) {
- int i = 1;
- char buf[4];
- strcpy(buf, "AAAA");
- printf("%d\n", i);
- return 0;
- }
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. 打印结果是多少?
答:参见具体的代码。(这个题目作为笔试题目是不是太简单了......有什么玄机?)
- #include <stdio.h>
- #include <string.h>
- int main(int argc, char *argv[]) {
- int w = 1, x = 2, y = 3, z = 4;
- int m;
- m = (w < x) ? w : x;
- m = (m < y) ? m : y;
- m = (m < 2) ? m : z;
- printf("m = %d\n", m);
- return 0;
- }
4. 说出结果:
答:参见具体代码。这题考察的是对文件的基本操作:
- #include <stdio.h>
- int main()
- {
- FILE *fp;
- int i, a[4] = { 1,2,3,4 }, b;
- fp = fopen("data.dat", "wb");
- for (i = 0; i < 4; i++) {
- fwrite(&a[i], sizeof(int), 1, fp);
- }
- fclose(fp);
- fp = fopen("data.dat", "rb");
- fseek(fp, -2L * sizeof(int), SEEK_END);
- fread(&b, sizeof(int), 1, fp);
- fclose(fp);
- printf("b = %d\n", b);
-
- return 0;
- }
5. 下面代码会输出123吗?123创建在堆上还是栈上呢?123的空间是什么时候释放的?
- #include <stdio.h>
- char * GetStr()
- {
- char *tmp;
- tmp = "123";
- return tmp;
- }
-
- void main()
- {
- printf("%s", GetStr());
- }
答:会。这里需要区别一点,“123”并不是像普通变量一样放在栈中的,而是存放在.rdata段中,它在编译阶段就确定了。如果编译出来是一个二进制,你可以在二进制中找到这个字符串。所以说,它并不会因为退出函数而消失,因此就能够打印出来。它的释放需要在程序结束的时候。
6. 字符指针、浮点数指针、以及函数指针这三种类型的变量哪个占用的内存最大?为什么?
答:都是指针,占用的一样大。具体见下面的代码:
- #include <stdio.h>
- int main(void)
- {
- char *p1;
- float *p2;
- void(*fp)(void);
- printf("p1: %d, p2: %d, fp: %d\n", sizeof(p1), sizeof(p2), sizeof(fp));
- return 0;
- }
7. char **p, a[16][8]; 问:p=a是否会导致程序在以后出现问题?为什么?
答:本身并不会报错,但是还是会存在问题。因为a的实际类型应该是char (*)[8],而不是p的char **。
8. 应用程序在运行时的内存包括代码区和数据区,其中数据区又包括哪些部分?
答:数据区包含堆栈等动态的数据,和全局变量、静态变量和字符串等静态数据,且字符串数据是只读的。
9. 用<<,>>,|,&实现一个WORD(2个字节)的高低位交换。
答:参见具体代码:
- #include <stdio.h>
- int main(void)
- {
- unsigned short num = 0xABCD;
- printf("num: 0x%x\n", num);
- unsigned char tmp;
- tmp = num >> 8;
- num = (num << 8) | tmp;
- printf("num: 0x%x\n", num);
-
- return 0;
- }
10. 写出程序运行结果:
答:参见具体代码,这里主要考察的是static在非初次进出函数时并不会重新赋值:
- #include <stdio.h>
- int sum(int a)
- {
- auto int c = 0;
- static int b = 3;
- c += 1;
- b += 2;
- return (a + b + c);
- }
- void main()
- {
- int i;
- int a = 2;
- for (i = 0; i < 5; i++){
- printf("%d\n", sum(a));
- }
- }
11. 写出函数func(1)的返回值:
答:参见具体的代码。这里主要考察的是switch的一种错误写法,忘了break:
- #include <stdio.h>
- int func(int a)
- {
- int b;
- switch (a)
- {
- case 1:
- b = 30;
- case 2:
- b = 20;
- case 3:
- b = 16;
- default:
- b = 0;
- }
- return b;
- }
-
- void main()
- {
- printf("%d\n", func(1));
- }
12. 写出程序运行的结果:
答:参见代码。本题考察的是指针的加减法运算:
- #include <stdio.h>
-
- void main()
- {
- int a[3];
- a[0] = 0; a[1] = 1; a[2] = 2;
- int *p, *q;
- p = a;
- q = &a[2];
-
- printf("%d\n", a[q - p]);
-
- }
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的使用是否有问题?
- void UpperCase(char str[])
- {
- for (size_t i = 0; i<sizeof(str) / sizeof(str[0]); ++i)
- if ('a' <= str[i] && str[i] <= 'z')
- str[i] -= ('a' - 'A');
- }
- char str[] = "aBcDe";
- cout << "str字符长度为: " << sizeof(str) / sizeof(str[0]) << endl;
- UpperCase(str);
- cout << str << endl;
答:函数中的sizeof有问题,因为这里的str是参数,它表示的是char *,而不能表示实际的数组,因此sizeof的值是4,而不是真正数组的大小。
注意,这里代码是c++的,并不是c。,不过不影响题目的意思。
21. 全局变量如int i=5; int*(pf)()=foo; 分别在何时被初始化?
答:全局变量的话,在编译的时候就初始化了。函数指针指向具体的函数,需要在链接的时候确定。
22. 以下代码运行结果是什么?
答:参见具体代码。这里考察的是变量的作用域:
- #include <stdio.h>
- int main()
- {
- int a, b, c, abc = 0;
- a = b = c = 40;
- if (c)
- {
- int abc;
- abc = a*b + c;
- }
- printf("%d,%d\n", abc, c);
- return 0;
- }
23. 请定义一个宏,比较两个数a、b的大小,不能使用大于、小于、if语句。
答:参见具体的代码:
- #define MAX(a,b) (((a)-(b))&(1<<31))?(b):(a)
24. 如何输出源文件的标题和目前执行行的行数。
答:使用预编译宏:
- printf("%s %d\n", __FILE__, __LINE__);
25. 全局变量和局部变量有什么区别?是怎么实现的?操作系统和编译器是怎么知道的?
答:一些变量在整个程序中都是可见的,它们称为全局变量。一些变量只能在一个函数中可知,称为局部变量。这就是他们的区别。在任何函数外面定义的变量就是全局变量,在函数内部定义的变量是局部变量,这是它们在程序中的实现过程。操作系统和编译器是根据程序运行的内存区域知道他们的,程序的全局数据放在所分配内存的全局数据区,程序的局部数据放在栈区。
26. 局部变量能够和全局变量重名?
答:可以,但是有覆盖。
27. 如何引用一个定义过的全局变量?
答:可以用引用头文件的方式,也可以用extern关键字。如果用引用头文件方式来引用某个在头文件中声明的全局变理,假定你将那个变量写错了,那么在编译期间会报错,如果你用extern方式引用时,假定你犯了同样的错误,那么在编译期间不会报错,而在链接期间报错。
28. 请写出下列代码的输出内容:
答:参见具体代码说明,这里主要考察符号优先级的内容:
- #include<stdio.h>
- main()
- {
- int a, b, c, d;
- a = 10;
- b = a++;
- c = ++a;
- d = 10 * a++;
- printf("b,c,d:%d,%d,%d\n", b, c, d);
- return 0;
- }
29. 以下代码的运行结果是:
答:参见具体代码:
- #include<stdio.h>
- #define product(x) ((x)*(x))
- main()
- {
- int i = 3, j, k;
- j = product(i++);
- k = product(++i);
- printf("%d %d\n", j, k);
- }
30. 以下代码的运行结果是:
答:参见具体的代码:
- #include<stdio.h>
-
- main()
- {
- unsigned char i = 0xA5;
- unsigned char j = ~i >> 4 + 1;
- printf("j = %d\n", j);
-
- }
首先执行过程已经写了:(~i)>>(4+1),但是还有另外一个需要注意的是,i的类型是unsigned char,它在~操作中会被提升为int,见具体的反汇编代码:
- unsigned char i = 0xA5;
- 0038178E C6 45 FB A5 mov byte ptr [i],0A5h
- unsigned char j = ~i >> 4 + 1;
- 00381792 0F B6 45 FB movzx eax,byte ptr [i]
- unsigned char j = ~i >> 4 + 1;
- 00381796 F7 D0 not eax
- 00381798 C1 F8 05 sar eax,5
- 0038179B 88 45 EF mov byte ptr [j],al
- printf("j = %d\n", j);
从上述的反汇编代码可以看到,i的值被传到了eax中,得到的结果是0x000000A5,再执行NOT之后变成了0xFFFFFF5A,在右移5位,变成了0xFFFFFFFA,然后再取其中的低8位,得到0xFA,即十进制的250。
31. 以下代码的运行结果:
- #include<stdio.h>
- main()
- {
- int arr[] = { 1, 2, 3, 4 };
- int *ptr = arr;
- printf("%d %d\n", *ptr, *(++ptr));
- }
答:vs2015中的打印是2 2。
之前网上看到的答案说“因为参数在函数的传递时是从右到左的,所以会先运行一遍++ptr”。
但是实际上c标准中并没有什么规定,所以其实并不一定会++ptr先运行,真正的答案应该是“不确定”...
32. 以下代码的运行结果:
- #include<stdio.h>
- main()
- {
- unsigned int a = 0xFFFFFFF7;
- unsigned char i = (unsigned char)a;
- char *b = (char *)&a;
- printf("%08x, %08x\n", i, *b);
- printf("%d, %d\n", i, *b);
- }
答:
000000f7, fffffff7
247, -9
需要注意fffffff7这个结果!
33. 下面代码的打印结果是什么:
- #include <stdio.h>
-
- typedef struct A
- {
- int num:2;
- }A;
-
- int main()
- {
- A a;
- a.num = 1;
- a.num++;
- printf("%d\n", a.num);
- return 0;
- }
这个题目有点意思,今天诺基亚面试的时候遇到的。它看似考察的是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