1.下面的代码输出是什么?为什么?
void foo(void)
{unsigned int a = 6; int b = -20;(a + b>6)? puts(">6"):puts("<=6");
}
答案:输出 >6
原因:当表达式中存在有符号类型和无符号类型时所有的操作数都自动转换为无符号类型。
6的补码:00000000 00000000 00000000 00000110
-20补码:11111111 11111111 11111111 11110010
相加后按照无符号形式输出:4294967282 >6。
2.下面的代码有什么问题?为什么?
void foo(void)
{char string[10],str1[10]; int i;for(i=0; i<10; i++) str1[i] = ’a’;strcpy(string , str1); printf(“%s”,string);
}
答案:程序运行崩溃
原因:strcpy在运行时只有碰到‘\0’时才会停下来,而字符数组string在初始化时末尾并没有‘\0’,所以在拷贝时会一直向后找‘\0’,直至访问越界。
3.下面的代码,i 和 j 的值分别是什么?为什么?
static int j;
int k=0;void fun1(void)
{static int i=0;i++;
}void fun2(void)
{j=0;j++;
}int main()
{for(k=0;k<10;k++){fun1();fun2();}return 0;
}
答案:i = 10;j = 1;
原因: i 声明在函数内部是局部变量,保存在内存栈当中。当它被关键字static修饰后,便保存在静态区且它的值只会初始化一次。当局部变量被static修饰后改变为静态变量,它的生存期变长,但作用域变。
j 声明在所有的函数之外,是全局变量。 当全局变量被static修饰后,变量的作用域变窄(只在当前文件中有效),且生命周期不变 (所以每被调用一次就会被初始化一次)
注意:函数形参不允许被static声明
另外,当函数被static修饰后改变的是函数的链接属性使其成为静态函数,函数的作用域仅局限于本文件(所以又称内部函数)。
4.下面代码里,假设在 32 位系统下,各 sizeof 计算的结果分别是多少?
int *p=NULL;
//sizeof(p)的值是 ------>4 p是一个指针变量,占四个字节
//sizeof(*p)的值是 ------>46
// sizeof括号内部的内容不参加运算,所以并不会计算*p是什么,由于p是int*类型的所以返回4
int a[100];
//sizeof(a)的值是 ------->400 数组名单独出现在sizeof中表示整个数组
//sizeof(a[100])的值是 ------->4
// sizeof括号内的表达式并不进行计算,只是计算类型的空间占用量,(a[])
//sizeof(*(a+100))的值是------->4 因为a是int型数组,这里并不计算a+100指向哪里
//sizeof(&a)的值是 ------->4 地址的内存占用量在32位机器下永远是4个字节(64位下为8字节)
//sizeof(&a[0])的值是 ------->4 同上int b[100];
void fun(int b[100])
{sizeof(b);
}
//sizeof(b)的值是 ------->4函数传参是只是传入一个地址,这里是求的是地址大小
5.下面代码的结果是多少?为什么?
int main()
{char a[1000]; int i;for(i=0; i<1000; i++) a[i] = -1-i;printf("%d",strlen(a)); return 0;
}
答案:255
原因:strlen函数统计元素个数的过程相当于一个找‘\0’的过程,只有在找到‘\0’后才会停止++,从而得到元素的个数。所以本题也就是一个在数组中找0的过程(‘\0’的ASCII码值为0)。根据循环初始化数组中的内容应该是:-1,-2,-3…..-128,127,126,125…….2,1,0所以一共有225个元素。对于为什么-128再往后是127可见下图表示:
6.下面的代码里,哪些内容可被改写,哪些不可被改写?
1.const int *p;
2.int const *p;
3.int *const p;
4.const int * const p;
答案:1.*p不能改,p可以改
2.*p不能改,p可以改
3.p不能改,*p可以改
4.*p和p都不能改
判别方法: 先忽略类型名,(编译器解析的时候也是忽略类型名),const 离哪个近就修饰谁
1.const int *p; 去掉int —–> const *p const 修饰*p, p 是指针,可变*p 是指针指向的对象,不可变。
2.int const *p; 去掉int —–> const *p const 修饰*p, p 是指针,可变*p 是指针指向的对象,不可变。
3.int *const p; 去掉int —–> *const p const 修饰 p,p 不可变,p 指向的对象可变。
4.const int * const p; 去掉int —–> const *const p 前一个 const 修饰*p,后一个 const 修饰 p,指针 p 和 p 指向的对象都不可变。
7.下面的两段代码有什么区别?什么时候需要使用代码(2)
//代码1:
int i = 10;
int j = i;//---------(1)
int k = i;//---------(2)//代码2:
volatile int i=10;
int j = i;//---------(1)
int k = i;//---------(2)
在代码1后两条语句中,i 没有被用作左值。这时候编译器认为 i 的值没有发生改变,所以在(1)语句时从内存中取出 i 的值赋给 j 之后,这个值并没有被丢掉,而是在(2)语句时继续用这个值给 k 赋值。编译器不会生成出汇编代码重新从内存里取 i 的值,这样提高了效率。但要注意:(1)、(2)语句之间 i 没有被用作左值才行。
volatile 关键字告诉编译器 i 是随时可能发生变化的,每次使用它的时候必须从内存中取出 i,的值,因而编译器生成的汇编代码会重新从 i 的地址处读取数据放在 k 中。
volatile 可以保证对特殊地址的稳定访问
8. 在 32 位的 x86 系统下,输出的值为多少?
#include<stdio.h>
int main()
{int a[5] = {1,2,3,4,5};int *ptr1 = (int *)(&a + 1); int *ptr2 = (int *)((int )a + 1);printf("%x,%x",ptr1[-1], *ptr2); return 0;
}
答案:5,2000000
9.0x01<<2+3 的值为多少?为什么?
答案:0x20(十进制32)
原因:因为加号运算符优先级高于右移运算符,相当于0x01右移五位,即2的5次方。
需要注意:左移和右移的位数不能超过数据的长度,也不能小于0
10.定义一个函数宏,求x的平方。
答案: #define SQR(x) ((x)*(x))
需要注意:宏的本质是字符串的替换,所以为了避免出现优先级问题一定要加括号保证运算的正确性。
宏函数与函数相比较:
属性 | define宏 | 函数 |
---|---|---|
代码长度 | 每次使用时,宏代码被插入到程序当中,除非宏内容很短否组会大幅增加代码量 | 函数代码只保存在一个地方,每次使用都调用那个位置的同一份函数代码 |
执行速度 | 更快 | 存在函数调用返回的额外开销 |
操作符优先级 | 宏参数设计时为避免临近操作符因优先级产生的不可预料的结果应该加上括号 | 函数参数只在调用时求值一次,其结果传递给函数。求值结果更容易预测 |
参数求值 | 参数每次用于宏定义时都会重新求值,由于多次求值带有副作用的参数可能会产生不可预料的结果 | 函数在被调用之前求值一次,多次使用参数也不会导致多种求职过程,带有副作用的参数不会产生特殊影响 |
参数类型 | 宏与类型无关,只要对参数的操作是合法的,他可以使用任何参数类型 | 函数参数与类型有关,如果类型不同就需要使用不同的函数 |
11.下面的两段代码有什么区别?
//代码1:
struct TestStruct1{char c1; short s; char c2; int i;
};//代码2:
struct TestStruct2{char c1; char c2; short s; int i;
};
答案:二者空间占用量不同。代码1占12byte,代码2占8byte
考察知识点:结构体内存对齐
结构体内存对齐规则:
- 结构体内的第一个成员在在结构体变量偏移量为0的地址处。
- 其他成员变量要对齐到对齐数的整数倍地址处
对齐数 = min( 编译器默认对齐数,成员大小 );windows环境下为4,Linux环境下为8 - 结构体总大小为最大对齐数的整数倍(每个成员都有自己的对齐数)
- 如果有嵌套结构体的情况,嵌套结构体对齐到自己的最大对齐数的整数倍处,结构体整体的大小就是所有最大对齐数的整数倍(包括被嵌套的结构体)
总结起来:存在内存对齐完全是一种用空间换时间的行为,如果既想要运行效率又要避免内存浪费最好的方法就是尽可能的把空间占用量小的成员放在一起。
offsetof宏函数可用于计算结构体成员相对于结构体的偏移量
12.写代码向内存 0x12ff7c 地址上存入一个整形数 0x100。
答案:
int *p = (int *)0x12ff7c;
*p = 0x100;
// 或者*(int*)0x12ff7c = 0x100;
原因:向一块内存中写入一个整型变量就要保证指向这块空间的指针是int型指针,若要写入一个char字符就可以将指针设置为char*类型。
13.下面的代码输出是多少?
main()
{int a[5]={1,2,3,4,5};int *ptr = (int *) (&a + 1); printf(“%d,%d”,*(a+1), *(ptr-1));
}
答案:(a+1) = 2,(ptr-1) = 5
14.假设 p 的值为 0x100000,如下表达式的值分别为多少?
struct Test
{int Num;char *pcName; short sDate; char cha[2]; short sBa[4];
}*p;
//p+0x1 = ?
//(unsigned long)p+0x1 = ?
//(unsigned int *)p+0x1 = ?
答案:
p+0x1 = 00000014
(unsigned long)p+0x1 = 00000001
(unsigned int *)p+0x1 = 00000004
原因:根据内存对齐(同11题)可计算出结构体的内存占用量为20字节,指针变量p是指向整个结构体的,即p的内存偏移量为20,所以p+0x1用16进制表示即为00000014。同理可得unsigned int * 的内存偏移量为4,所以(unsigned int * )p+0x1 = 00000004。(unsigned long)p是将地址 0x100000以无符号的长整型读出,+1就只是在原本值上+1即可,所以(unsigned long)p+0x1 = 00000001。
15.下面代码输出地结果是多少?
#include<stdio.h>
int main(int argc,char * argv[])
{int a[3][2]={(0,1),(2,3),(4,5)}; int *p;p=a[0]; printf("%d",p[0]);
}
答案:1
原因:由于二维数组是由一维数组组成的,所以在对二维数组进行初始化时花括号内嵌套的必须是花括号。若是在花括号内用圆括号“( )”进行初始化,且其中含有逗号,编译器会将其按照逗号表达式进行处理。所以数组a中真正定义的内容是int a[3][2]={1,3,5};
16.下面的代码有什么问题?为什么?
void fun(char a[10])
{char c = a[3];
}
int main()
{char b[10] = “abcdefg”; fun(b[10]);return 0;
}
答案:一维数组传参错误,a[10]表示的是数组的内容(越界)而非char*形参所需的类型。系统虽然传入不会报错但是会有警告。
分析:C语言中,当一维数组做函数参数时,编译器总是把它解析成一个指向其首元素的指针,所以函数实际传递的数组大小与函数形参指定的数组大小没有关系。
一维数组传参的两种形式:
- void fun( char a[] )
- void fun( char* a )
17.下面的代码有什么问题?为什么?
struct student
{char *name; int score;
}*pstu;
int main()
{pstu = (struct student *)malloc(sizeof(struct student)); strcpy(pstu->name, “Jimy”);pstu->score = 99;free(pstu);return 0;
}
答案:运行错误:
error7.exe 中的 0x5b33f689 (msvcr90d.dll) 处未处理的异常: 0xC0000005: 写入位置 0xcdcdcdcd 时发生访问冲突。
在定义结构体变量 stu 时,对于结构体内部 char *name 成员只是给 name 这个指针变量本身分配了 4 个字节。name 指针并没有指向一个合法的地址,这时候其内部存的只是一些乱码。所以在调用 strcpy 函数时,会将字符串”Jimy”往乱码所指的内存上拷贝,而这块内存 name 指针根本就无权访问,导致出错。解决的办法是为 name 指针 malloc 一块空间。
在为结构体指针分配内存时是从外向里,即先分配结构体的指针再分配成员指针.释放时从里向外,先释放成员指针再释放结构体指针。
修改:
int main()
{ pstu = (struct student *)malloc(sizeof(struct student)); pstu->name = (char *)malloc(20); strcpy(pstu->name,"Jimy"); pstu->score = 99; free(pstu->name); //free()函数并不能将指针指向空,在free掉之后应该手动将开辟的空间指针指向空。pstu->name = NULL; free(pstu); pstu = NULL; return 0;
}
18.下面的代码输出结果是多少?
void fun(int i)
{if( i > 0 ) fun(i/2);printf("%d\n",i);
}
int main()
{fun(10);return 0;
}
答案:
原因:这是一个简单的递归函数,printf函数在递归之后,所以每一层递归返回都会调用一次printf函数。
19.下面的代码有什么问题?为什么?
char c;
c = getchar();
if(EOF == c)
{
…
}
答案:由于getchar函数的返回值是int型,如果用char类型进行接收的话可能会发生信息遗漏。
关于getchar():
返回值:getchar函数的返回值是用户输入的第一个字符的ASCII码,当出错时返回-1。并且将用户输入的字符回显到屏幕。
如果用户在按回车之前输入了不止一个字符那么其他字符会保留在键盘缓存区中,等待后续getchar调用读取。(这也是为什么有的main函数中的最后一句会是getchar()的原因)也就是说,后续的getchar调用不会等待用户按键,而直接读取缓冲区中的字符,直到缓冲区中的字符读完为后才等待用户按键。
20.请写一个C函数,若当前系统是Big_endian的,则返回0;若是little_endian的,则返回1。
本题的意思即写一个小函数测试数据在当前编译器下是“大端”存储还是“小端”存储
- 大端模式:是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中
- 小端模式:指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中,
//方法一
int main()
{int a = 1;int ret = *(char*)&a;printf("%d",ret);//输出为1为小端,0则为大端return 0;}
//方法二
int checkSystem()
{union check{int i;char ch;}c;c.i = 1;return (c.ch == 1);
}//输出为1为小端,0则为大端
以上内容均为自行学习总结内容,如有错误欢迎批评指正。