一个“简单”的C++问题

作为一个没有正经用 C++ 写过项目的码农,在面试到 C++ 候选人时,尽管心虚,但我也只能鼓起勇气,假装自己很熟 C++ 。

不过每当在简历上看到说自己 “精通” C++ 的时候,还是免不了心里呵呵一下,然后想起早年百度 C++ 规范的开头那句话:

不要使用 C++ 。

毕竟有个我觉得挺基础的 C++ 问题,能正经答出来的人,真的不多。


= 题面 =

题目从一个简单的 struct 开始:

struct X {
  int a;
  char b;
};

问:可以编译、运行如下代码么?

struct X *p = NULL;
p++;

思考一下?


= 回答 =

在犹豫一会以后,绝大部分人的回答是:

“可以编译,但不能正常运行”。

进一步询问为什么不能正常运行时,给出的理由大概意思是,空指针是无效的(或者说,没有指向可以修改的内存空间),所以不能修改。

如果你也是这么觉得的,可以再思考一下。


= 解析 =

题面确实有一些令人迷惑,所以很多人思考后仍然给出了上述回答。

他们的问题是,混淆了【指针的值】和【指针指向的对象】。

p 的值是 NULL ,确实指向了一个无效的对象,但是在 "p++" 这个操作中,我们修改的并不是其指向的对象,而是 p 指针本身。

本质上讲,(在常见的CPU架构,如 x86/x86-64/arm 等)指针等价于一个 long 类型,对指针的值进行操作,只是修改了指针指向的对象。


= 再问 =

按照一贯的套路,针对这个问题我还准备了追问:

“执行完后,p 的值预计是多少?”

不过鉴于大部分人都认为前述代码无法正确执行,所以往往只能这么问:

“用 sizeof(struct X) 求 X 的大小,预计可以得到多少?”

因为在 C/C++ 里,对一个指针的自增操作(即++操作符),其含义是将指针指向内存中连续的下一个对象。

这其实是个很开放的问题,除了少部分奇怪的回答之外,大部分人的回答都可以是正确的。

但重要的是为什么是正确的。

比如有些人的回答是 5,因为 int 占用 4 个字节, char 占用 1 个字节。

这当然可以没错 —— 只不过默认情况下,大部分编译器的行为并非如此。


= 再解析 =

例如下图,用 gcc -S 处理左边的 C 代码,生成对应的汇编:

可以看出,生成的机器码,是给 p 加上 8,而不是 5。

注:给不熟悉 gcc 汇编的同学解释一下

  • rbp 是栈寄存器,%rbp 是栈顶地址

  • -16(%rbp) 表示 p 存在栈顶 -16 个字节的位置

  • rcx 是 64 bit 寄存器(相对于 x86 的 cx 寄存器,32位)

  • $8 是常量 8

  • 右边红框的意思是,将 p 的值从“栈顶-16”内存中拷贝到 rcx 寄存器中,+8,然后再写回内存。

前面说“可以没错”,是因为我们可以用 pragma 预处理指令改变编译器的行为:

可以看到,生成的汇编代码里, $8 变成了 $5 。

实际上这里还可能出现一些其他的答案,比如在 16 bit CPU 下,int 占用2个字节,或者你也可以用 pragma pack(2) 试试看。

所以重要的是为什么


= 还问 =

编译器默认 8 字节的行为被称为“alignment”(内存对齐),很多人在给出这个答案的同时,也会顺口说到这个概念。

于是我可以继续问,内存对齐的意义是什么?

但是显然大多数人都还给老师了……

简而言之,这又是一个空间换时间的 case —— 在常见的 x86 架构下,从奇数地址读取一个 int 数据,会需要读取两次内存,并且将两次读取的数据拼起来,这显然会降低执行效率。

也许有的同学会觉得,即使知道这个概念,好像也没什么用,就像屠龙之术。

其实这就像《踩坑记:go服务内存暴涨》里面提到的内存管理知识一样,看起来好像大部分情况用不到,但真的遇到相关问题时,就会束手无策。

比如像 arm、ppc 这类 CPU 架构,其部署环境往往内存较少(有时会需要用 KB 作为单位),程序员会希望通过减少内存对齐来降低内存消耗;但需要注意的是,其不支持直接读写奇数地址,即使强制生成完全不对齐代码,执行往往会出错。

再比如,通过合理安排 struct 中成员的位置,也可以显著降低对内存的消耗。

例如 X、Y 这样两个 struct,包含相同的成员变量,但只是简单兑换一下 b、c 的位置,就可以节省 25% 的内存占用:

注:Y、Z 分别需要占用 16、12 字节

想想看,为什么 Z 不是只占用 10 个字节?


= 不问了不问了 =

最后得承认,这篇有点标题党了,实际上这是一个 C 问题,并不是 C++ 问题。

不过对于熟悉 C++ 的同学,没能正确回答这个问题实在让我有点意外,可能是我又陷入了知识的诅咒了(知道一个知识以后、就很难再想象不知道它的情况)。

你们怎么看这个题目呢?欢迎留言讨论。


推荐阅读:


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值