1 引言

本文主要总结了笔者自己的开发中遇到的不规范的代码写法。这些代码风格可能并不会引起错误,但是会产生隐患或容易引起歧义。
这些语法缺陷可以通过合理配置编译器的编译选项检查出来,对于编译器的警告配置在前面有文章进行了简单介绍。

2 代码缺陷总结

2.1 格式化字符串与后续参数不匹配

函数原型如下

int printf(const char* format, ...);
int sprintf(char* str, const char* format, ...);

2.1.1 size_t 类型

由于 size_t 在不同平台上会有不同的类型定义,合适的做法是使用 zsize_t 进行格式化。
变量宽度标识符 z 需要编译器支持C++11或C99特性。
示例代码如下:

size_t n = sizeof(char);
printf("the size of char is %zd\n", n); // decimal
printf("the size of char is %zx\n", n); // hex

2.1.2 int64_t 及其相关类型

int64_t 是固定宽度为8字节的整形定义,在文件 <cstdint> 中有定义。
类似的还有 int8_t, int16_t, int32_t 分对应1,2,4字节的整形定义。
他们的格式化字符串使用 PRId8, PRId16, PRId32, PRId64 表示,他们在文件 <cinttypes> 中有定义。
阅读源码后底层实现原理是通过识别系统中字宽,通过宏定义将满足不同条件的情况定义出来。
同样这些特性也是从C++11和C99开始才引入的。
示例代码如下:

#include <cstio>
#include <cinttypes>

std::printf("%zu\n", sizeof(std::int64_t));
std::printf("%s\n", PRId64);
std::printf("%+" PRId64 "\n", INT64_MIN);
std::printf("%+" PRId64 "\n", INT64_MAX);

std::int64_t n = 7;
std::printf("%+" PRId64 "\n", n);

参考资料:http://en.cppreference.com/w/cpp/types/integer

2.1.3 %x 对应无符号整数

在使用 sscanf 函数对取16进制数据时需要注意传入的需要是无符号类型的地址。
示例代码如下:

const char* szNumber = "af"
unsigned int = nNum;
sscanf(szNumber, "%x", &nNum);

2.2 多个逻辑表达式之间在 &&|| 之间的运算符优先级

先看以下示例代码:

if (EXPR1 || EXPR2 || EXPR3 && EXPR4)
{
    // ....
}

分析:从语法规则上来说逻辑与( && )的运算符优先级要高于逻辑或( || )。
所以在进行运算时,会先计算逻辑与,再计算逻辑或。

从语法上分析是没有错误的,但是这样的表达式很容易造成读者4的误解。
改成以下的形式会好很多:

if (EXPR1 || EXPR2 || (EXPR3 && EXPR4) )
{
    // ....
}

2.3 有符号整数和无符号整数之间的大小比较

有符号整数和无符号整数在进行大小比较时,有符号数会被转化为无符号数。
这样会引起一些不必要误解,所以应该避免这种用法。
示例代码如下:

int n1 = -1;
unsigned int n2 = 1;
bool bRet = n1 < n2; // bRet = false

注意,这里的运算结果与实际预期不符。

另外,使用无符号数作为循环的迭代条件时,需要注意进行迭代条件在比较时是否能够正常终止迭代。
示例代码如下:

for ( size_t i=100; i>0; --i )
{
    // ...
}

注意,这里会出现死循环。

2.4 函数的重载和覆盖

在进行虚函数多态实现时,注意检查函数的一致性。
出现相同函数名称但是积累为非 const 版本,而派生类为 const 时,这时并不会导致多态,而会导致基类函数被隐藏。