《c语言深度解剖》--一套非常经典的笔试题(2)

简介: 学习完c语言,需要对所学知识进行一个检测,下面有一套笔试题,你有四十分钟进行检测,每道题五分,严格要求自己打分。根据作者原话:在没有何提示的情况下,如果能得满分,那你可以扔掉本书了,因为你的水平已经大大超过了作者;如果能得80分以上,说明你的C语言基础还不错,学习本书可能会比较轻松;如果得分在50分以下,也不要气馁,努力学习就行了;如果不小心得了10分以下,那就得给自己敲敲警钟了;如果不幸得了0分,那实在是不应该,因为毕竟很多题是很简单的。

148979d535e141bca08c0270c221c58c.png

6.(1)const放在p之前,限制的是 * p,所以p不可更改,但是p未被限制,所以p指向的对象可以更改。

(2)同(1),同样放在*p之前,结果同(1)

(3)const放在p之前,p之后,限制的是p,所以p指向的对象不可更改,p可以更改

(4)p和p都被限制了,所以p指向的对象和p都不可更改。

1bea88be38a74c2e8da518f29113e0c1.png

7.代码(1):这时候编译器对代码进行优化,因为在代码(1)的两条语句中,i没有被用作左值(没有被赋值)。这时候编译器认为i的值没有发生改变,所以在第1条语句时从内存中取出i的值赋给j之后,这个值并没有被丢掉,而是在第⒉条语句时继续用这个值给k赋值。编译器不会生成出汇编代码重新从内存里取i的值(不会编译生成装载内存的的汇编指令﹐比如ARM的LDM指令),这样提高了效率。但要注意:两条语句之间i没有被用作左值(没有被赋值)才行。


代码(2)的使用时机:如果i是一个寄存器变量、表示一个端口数据或者是多个线程的共享数据,那么就容易出错,所以说

volatile可以保证对特殊地址的稳定访问。


代码(2)的使用时机:如果i是一个寄存器变量、表示一个端口数据或者是多个线程的共享数据,那么就容易出错,所以说

volatile可以保证对特殊地址的稳定访问。

e51284d899934ff4be290151b89bd29f.png

8.这道题需要画图理解。结果是 5,2000000

7a80bd2af0e142d3814990ae64fa657f.png

数组a的5个元素的十六进制表示形式在内存中的布局如上图:

对于ptr1来说,&a取到了整个数组的地址,+1跳过了整个数组,再强转成int*,所以ptr1指向的是数组的后一位的地址。如下图:

c91d214e722c4c7d9e975b1134f8b116.png

以16进制的形式打印ptr[-1]时,其实可以理解成打印 *(ptr-1),指针-1,跳过指针所指向对象的类型,ptr1指向的对象是int类型,所以-1跳过一个int。如下图:

e2977be88dff4b6b9aad4f709793108f.png

指针指向的对象是一个int类型,所以一次取出4个字节。

由于计算机是小端存储模式,所以,低位放在低地址处,高位放在高地址处,拿出来就是 00 00 00 05,以十六进制的形式打印,5前面的0是不显示的,没什么意义,所以打印出来就是5

对于ptr2来说,a是数组首元素地址,强转成(int),所以a的地址就被强转成了一个整型,假设a的地址为:0x0012ff40,该地址就被强转成了一个整型,此时0x0012ff40是一个整型,整型+1之后,就是0x0012ff41,加了一个字节(整型+1就是+1)。0x0012ff41再强转成一个int*的指针赋给ptr2。此时ptr2指向的位置如下图:

e368683beb864653ae7215fb78f9f40e.png

ptr2指向的对象是int类型,所以ptr2一次性会读取4个字节,即读取

00 00 00 02,由于计算机是小端存储,低位放在低地址,高位放在高地址。


所以以十六进制的形式表示是0x 02 00 00 00,以十六进制的形式打印出来是2 00 00 00,(02中的0会被去掉)

67a1f8e4d25843d4a09291ef25c9c008.png

9.值为32

+号的优先级高于<<,先计算2+3,再计算0x01<<, 即

0x01<<5,

将0x01的十六进制补全是 0x00 00 00 01,转换成二进制的形式为:

00000000 00000000 00000000 00000001

<<操作符移动的是二进制位,左移5位后变成:

00000000 00000000 00000000 00100000

转换成十进制是32

b568382b77fe400787e6545306662a9f.png

10.答案:

#define SQR(x) ((x)*(x))

然而,该宏是有缺陷的,如果传入x++,++x这样有副作用的参数,宏就会出现问题。

(指出宏有缺陷者满分)

ee3f94e542374b74b22f3ea566b59e10.png

11.结构体在内存中的存储较为复杂,具体请看

结构体内存对齐

这里直接说明答案即可:

(在未说明的情况下,默认对齐数为8)

代码(1)的结构体大小为12字节,代码(2)的结构体大小为8字节。

087c2c00d922471a916643f4b03d59f6.png

12.实际上,这道题目是存在一点问题的,实际中不能随便找一个地址,就对该地址的内容进行访问,这是野指针问题,是非法访问的问题,对编译器来说这是违法行为。

相当于题目让你闯红灯,闯红灯本来就是违法行为。(这里没有针对任何人,只是就本道题说一下我的看法);

可能题目是想让写题人知道指针的强大和灵活,让写题人重视指针的运用。

不过如果真的要写,就是下面的写法:

int *p = (int*)0x12ff7c;
*p = 0x100;


3f898061ca904a83887e1d06f8b0697c.png

13.

a表示数组首元素地址,a+1跳过了一个元素的大小,所以a+1就是a[1]的地址,*(a+1) 就拿到了a[1],所以结果是1。

对ptr来说,&a取到的是整个数组的地址,+1跳过了整个数组,所以ptr指向了元素5的后一位, * (ptr-1) 就指向5,打印出来就是5。

d19886d535964b479720c9a3a74e4a03.png

14. 0x100014,0x100001,0x010004

(1)

首先需要知道结构体的大小是多少,由上面结构体内存存储模式可知,该结构体大小为20字节。

p是一个结构体指针,指针+0x1,跳过了一个结构体的大小,所以p+1跳过了20字节,因为p的值为0x100000,所以p+1为0x100014,(20)转换为十六进制是14。

(2)

 将p强转成unsigned long类型后,p的值就是一个长整型,即0x100000是unsigned long类型,整型+1就是+1.

结果为:0x100001

(3)将p强转成unsigned int*的指针,+1后跳过一个int类型,所以结果为0x100004

0749daddbe4e4739b7d7db41c32c4779.png

15.考察逗号表达式。

(0,1)计算的结果是1,(2,3)计算结果是3,(4,5)计算结果是5

a初始化后的结果为:

1 2
5 0
0 0

a[0]就是第一行元素的地址,此时p就是指向第一行元素的地址,然后打印p[0],相当于找到了第一行第一个元素,打印结果为1

fe071bdabe11432dbc926b9d67cafe14.png

16.(1)传参的时候有问题,b[10]实际上是不存在的。

(2)fun的参数部分,如果它是想对数组的a[3]进行修改,传参应该传b即可,因为b是数组名,数组名表示首元素地址,类型是char* ,想改变它的值,应该使用char*的指针来接收。


编译器会有一个警告,说类型不匹配,因为b[10]的类型是char,而函数参数类型是char*。

1326e5116ac844bb9695984c2043292b.png

17.(1)在使用完pstu后没有置空,这是一个野指针问题。

(2)结构体的成员name也是一个指针,代码并没有给该指针申请空间,直接使用name会出现越界访问问题.

c882da56cc7c46d2a03a512bad1a71e7.png

18,典型的递归问题,直接上答案:

0 
1 
2 
5 
10

4adf314e2a7a47109376c49d0599f94e.png

19.

getchar函数的返回类型是一个int,但是c是一个char类型,将一个int类型放入char类型中会发生截断。‘

假如读取失败,getchar会返回EOF,EOF是一个宏,其本质是-1,

但有可能getchar读取失败返回的EOF不再-128~127这个范围内,导致if条件判断语句不进入,存在潜在的危险。

9b63a6c0dc264c3b86caa00168021d9a.png

20.方法1:

int main()
{
  int a = 1;
  int ret = *((char*)&a);
  if (ret == 1)
  {
    printf("小端\n");
  }
  else
  {
    printf("大端\n");
  }
}

方法2:

union un
{
  int a;
  char c;
}s;
int main()
{
  s.a = 1;
  if (s.c == 1)
  {
    printf("小端\n");
  }
  else
  {
    printf("大端\n");
  }
  return 0;
}

在没有何提示的情况下,如果能得满分,那你可以扔掉本书了,因为你的水平已经大大超过了作者;如果能得80分以上,说明你的C语言基础还不错,学习本书可能会比较轻松;如果得分在50分以下,也不要气馁,努力学习就行了;如果不小心得了10分以下,那就得给自己敲敲警钟了;如果不幸得了0分,那实在是不应该,因为毕竟很多题是很简单的。


相关文章
|
6月前
|
存储 编译器 C语言
从C语言到C++_34(C++11_下)可变参数+ lambda+function+bind+笔试题(下)
从C语言到C++_34(C++11_下)可变参数+ lambda+function+bind+笔试题
91 5
|
3月前
|
存储 小程序 C语言
C语言数据的存储(内含百度笔试题)
C语言数据的存储(内含百度笔试题)
40 4
|
5月前
|
程序员 C语言 C++
【C语言基础】:动态内存管理(含经典笔试题分析)-2
【C语言基础】:动态内存管理(含经典笔试题分析)
|
5月前
|
程序员 编译器 C语言
【C语言基础】:动态内存管理(含经典笔试题分析)-1
【C语言基础】:动态内存管理(含经典笔试题分析)
|
6月前
|
存储 C语言 Python
从C语言到C++_24(二叉搜索树)概念+完整代码实现+笔试题(下)
从C语言到C++_24(二叉搜索树)概念+完整代码实现+笔试题
73 3
|
6月前
|
算法 编译器 C语言
从C语言到C++_34(C++11_下)可变参数+ lambda+function+bind+笔试题(中)
从C语言到C++_34(C++11_下)可变参数+ lambda+function+bind+笔试题
58 2
|
6月前
|
算法 编译器 C语言
从C语言到C++_34(C++11_下)可变参数+ lambda+function+bind+笔试题(上)
从C语言到C++_34(C++11_下)可变参数+ lambda+function+bind+笔试题
39 1
|
6月前
|
C语言
从C语言到C++_24(二叉搜索树)概念+完整代码实现+笔试题(中)
从C语言到C++_24(二叉搜索树)概念+完整代码实现+笔试题
41 1
|
5月前
|
程序员 C语言 C++
C语言学习记录——动态内存习题(经典的笔试题)、C/C++中程序内存区域划分
C语言学习记录——动态内存习题(经典的笔试题)、C/C++中程序内存区域划分
128 0
|
6月前
|
算法 测试技术 C语言
从C语言到C++_24(二叉搜索树)概念+完整代码实现+笔试题(上)
从C语言到C++_24(二叉搜索树)概念+完整代码实现+笔试题
64 0