Preface

最近在使用 go 重构C++旧项目, 发现一个旧代码的bug,很有意思;下面展示一下简化的代码:

1
2
3
4
    int64_t ip = 12345678;
    std::string str;
    str = ip;
    std::cout << "str = " << str << std::endl; // str = N

我的第一感觉是: 第三行应该编译报错吧,于是写了个hello world,然后g++ -Wall ..., 竟然编译过了还能正常运行, 神奇吧。

于是探其究竟,原来string重载了赋值符号=, 支持单个字符 char 的赋值,int64在这里发生了窄转换。 源码如下:

1
2
3
4
basic_string& operator=(_CharT __c) {
    this->assign(1, __c);
    return *this;
}

这就解释了为啥输出字符"N", int64转换成 char,仅保留了最低的一个字节 Dec(78), 正好对应ASCII的 ‘N’。

问题到这里应该就结束了,等等,我们怎么规避这种问题?

再上一层楼

为啥 g++ -Wall ... 不提示这个 Narrow-Cast的警告? 而且 llvm-g++ 也不提示? 上 google 一顿操作猛如虎,原来 -Wall 是一组 warn 的集合,并不包含 -Wconversion。 加上之后,果然就会提示了:

1
2
3
main.cpp:20:8: warning: conversion to char from int64_t {aka long int} may alter its value [-Wconversion]
    str = ip;
        ^

借助初始化列表

在 c++11 中,我们还可以通过 initializer list 来赋值,因为初始化列表默认开启了 -Wnarrowing 等警告, 一旦检测到 narrow-cast,就会默认提示警告。

1
2
3
str = {ip};
        ^
main.cpp:20:8: warning: narrowing conversion of ip from int64_t {aka long int} to char inside { } [-Wnarrowing]

总结

在编译 c++ 工程时,建议同时加上 -Wall -Wconversion 等选项,让编译器帮助提示 可能出bug的代码; C++要注意的坑确实不少,写多了就会深有体会,哈哈哈。