C++项目中的各种坑【2017.10.30】

来源:互联网 发布:知乎手机端发表文章 编辑:程序博客网 时间:2024/05/22 00:55

C++项目中的各种坑

更新时间 2017.10.30

最近做C++项目的时候,踩了许多坑。想着如果能够将它们记录下来,整理在案,也算是不错的总结。于是写下此篇。

2017.10.30

for 循环的判断会重新计算

一个比较基础的问题了,但是不注意可能会踩坑。

int c = 8;for (int i = 0; i < c; i++) {  c--;}

for 循环不会保存 c 的值,每次都要计算表达式 (i < c)。
所以如果在循环中修改了判断时引用的变量(或者判断时调用的函数是不纯的),那么需要警惕。(STL容器进行for循环时,判断 end() 恰巧利用了这一点。)

2017.10.19

Linux 下 printf 输出不正常 (内嵌汇编的坑)

在写 JitFFI 的时候,为了测试 long double 的传递特性,书写了下面的代码:

void print_ld(long double ld) {    printf("%Lf\n", ld);    printf("0x%llX\n", *(uint64_t*)&ld);    printf("0x%llX\n", *((uint64_t*)&ld + 1));}void caller(void) {    asm("sub $0x8, %rsp");    asm("push $0x3fff");    asm("mov $0x8000000000000000, %rax");    asm("push %rax");    asm("call print_ld");    asm("add $0x18, %rsp");}int main(void){    caller();    return 0;}

正常情况下,caller 函数如下书写:

void caller(){    print_ld(1.0);}

应该会输出

1.0000000x80000000000000000x3FFF

但是实际上是(本机测试结果):

0.0000000x80000000000000000x3fff

反汇编以后,对比内嵌汇编版本与正常版本,发现 main 的代码有点不一样:

正常版本:

sub $0x8, %rspcall callermov $0x0, eaxadd $0x8, %rspret

内嵌汇编于 caller 的版本:

call callermov $0x0, eaxret

因为x64要求在调用函数时,%rsp 与16字节对齐,所以调用 print_ld 函数时,内嵌版本会出现对齐错误。print_ld 调用 第一个 printf 时,错误才显现出来。

以上案例告诉我们,内嵌汇编不要随便写。。

2017.10.16

Linux 下死循环导致死机

重构代码的时候,重写了一个带有循环函数。测试时候出现死循环导致死机。

解决办法:
在没有把握的情况下,加上assert用于测试。

int JC = 0;while (true){    assert(JC++ > 100000);}

保存 std::initializer_list 导致引用失效

保存 std::initializer_list 可能会出现引用失效的问题。
错误示例如下:

class L{public:    L(const std::initializer_list<int> &list)        : list(list) {}private:    std::initializer_list<int> list;}

解决办法:不保存std::initializer_list

隐式转换导致的各种数值错误

错误示例:

using byte = uint8_t;void print(byte v){    printf("%d\n", v);}print(233); // Error!

解决办法:
1. 重视 warning
2. 采取显式命名的方式:

void print_byte(byte v){    printf("%d\n", v);}

printf 输出参数不加 \n

printf 输出参数不加 \n,大致有两种错误形式。
一种是两个参数混杂在一起,一种是在Linux下不能即时输出。

void print(int v){    printf("%d", v);}print(5);print(6);  // Output : 56

这种混杂在一定情况下可能是我们希望看到的,但是大部分情况都会扰乱视听,消耗巨大时间排除bug.

原创粉丝点击