bugprone-sizeof-expression¶
该检查会查找最有可能出错的 sizeof
表达式使用情况。
The sizeof
运算符返回其操作数的大小(以字节为单位),操作数可以是表达式,也可以是类型名称的圆括号。
Suspicious usage of ‘sizeof(K)’¶
一个常见的错误是查询整型字面量的 sizeof
。这等同于查询其类型的尺寸(可能是 int
)。程序员的意图可能是获取整数本身,而不是其大小。
#define BUFLEN 42
char buf[BUFLEN];
memset(buf, 0, sizeof(BUFLEN)); // sizeof(42) ==> sizeof(int)
Suspicious usage of ‘sizeof(expr)’¶
在某些情况下,使用枚举或整数表示类型,常见的错误是查询表示类型的整数或枚举的 sizeof
,这会导致返回整数的大小,而不是整数所表示的类型的大小。
enum data_type {
FLOAT_TYPE,
DOUBLE_TYPE
};
struct data {
data_type type;
void* buffer;
data_type get_type() {
return type;
}
};
void f(data d, int numElements) {
// should be sizeof(float) or sizeof(double), depending on d.get_type()
int numBytes = numElements * sizeof(d.get_type());
...
}
Suspicious usage of ‘sizeof(this)’¶
The this
关键字会被评估为指向给定类型对象的指针。表达式 sizeof(this)
将返回指针的大小。程序员很可能想要的是对象的大小,而不是指针的大小。
class Point {
[...]
size_t size() { return sizeof(this); } // should probably be sizeof(*this)
[...]
};
Suspicious usage of ‘sizeof(char*)’¶
使用 char* A = ""
和 char A[] = ""
声明字符串字面量之间存在细微差别。第一种情况的类型是 char*
,而不是聚合类型 char[]
。在用 char*
类型声明的对象上使用 sizeof
将返回指针的大小,而不是字符串字面量中的字符数(字节数)。
const char* kMessage = "Hello World!"; // const char kMessage[] = "...";
void getMessage(char* buf) {
memcpy(buf, kMessage, sizeof(kMessage)); // sizeof(char*)
}
Suspicious usage of ‘sizeof(A*)’¶
一个常见的错误是计算指针的大小而不是其指向对象的大小。这些情况可能由于显式强制转换或隐式转换导致。
int A[10];
memset(A, 0, sizeof(A + 0));
struct Point point;
memset(point, 0, sizeof(&point));
Suspicious usage of ‘sizeof(…)/sizeof(…)’¶
除以 sizeof
表达式通常用于获取聚合的元素数量。此检查会针对不兼容或可疑情况发出警告。
在以下示例中,实体占用 10 个字节,与类型 int
不兼容,类型 int
占用 4 个字节。
char buf[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; // sizeof(buf) => 10
void getMessage(char* dst) {
memcpy(dst, buf, sizeof(buf) / sizeof(int)); // sizeof(int) => 4 [incompatible sizes]
}
在以下示例中,表达式 sizeof(Values)
返回 char*
的大小。很容易被其声明所迷惑,但在参数声明中,大小 '10' 被忽略,函数接收的是一个 char*
。
char OrderedValues[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
return CompareArray(char Values[10]) {
return memcmp(OrderedValues, Values, sizeof(Values)) == 0; // sizeof(Values) ==> sizeof(char*) [implicit cast to char*]
}
Suspicious ‘sizeof’ by ‘sizeof’ expression¶
将 sizeof
表达式相乘通常毫无意义,很可能是一个逻辑错误。在以下示例中,程序员使用了 *
而不是 /
。
const char kMessage[] = "Hello World!";
void getMessage(char* buf) {
memcpy(buf, kMessage, sizeof(kMessage) * sizeof(char)); // sizeof(kMessage) / sizeof(char)
}
此检查可能会在使用 arraysize 宏的代码上触发。以下代码运行正常,但可以通过仅使用 sizeof
运算符进行简化。
extern Object objects[100];
void InitializeObjects() {
memset(objects, 0, arraysize(objects) * sizeof(Object)); // sizeof(objects)
}
Suspicious usage of ‘sizeof(sizeof(…))’¶
获取 sizeof
的 sizeof
毫无意义,通常是通过宏隐藏的错误。
#define INT_SZ sizeof(int)
int buf[] = { 42 };
void getInt(int* dst) {
memcpy(dst, buf, sizeof(INT_SZ)); // sizeof(sizeof(int)) is suspicious.
}
Suspicious usages of ‘sizeof(…)’ in pointer arithmetic¶
对指针的算术运算符会自动使用指向类型的尺寸缩放结果。在指针算术运算周围进一步使用 sizeof
通常会导致意外结果。
Scaling the result of pointer difference¶
减去两个指针会导致一个整数表达式(类型为 ptrdiff_t
),它表示两个指向对象之间的距离(以“对象数量”为单位)。一个常见的错误是认为结果是“字节数量”,并使用 sizeof
缩放差异,例如 P1 - P2 == N * sizeof(T)
(而不是 P1 - P2 == N
)或 (P1 - P2) / sizeof(T)
而不是 P1 - P2
。
void splitFour(const Obj* Objs, size_t N, Obj Delimiter) {
const Obj *P = Objs;
while (P < Objs + N) {
if (*P == Delimiter) {
break;
}
}
if (P - Objs != 4 * sizeof(Obj)) { // Expecting a distance multiplied by sizeof is suspicious.
error();
}
}
void iterateIfEvenLength(int *Begin, int *End) {
auto N = (Begin - End) / sizeof(int); // Dividing by sizeof() is suspicious.
if (N % 2)
return;
// ...
}
Stepping a pointer with a scaled integer¶
相反,在执行指针算术运算以对指针进行加减时,算术运算符会隐式地使用指向对象的尺寸缩放实际加到指针的值,例如 Ptr + N
期望 N
是“要跨越的对象数量”,而不是“要跨越的字节数量”。
看到包含 sizeof
的指针计算结果可疑,结果通常是意外的,经常超出边界。 Ptr + sizeof(T)
会将指针偏移 sizeof(T)
个元素,实际上将缩放因子提高到 2 的幂。
同样地,将数值乘以或除以元素或整个缓冲区的 sizeof
可疑,因为数值与实际 sizeof
结果之间的维度关系并不总能推断出来。虽然用 sizeof
将整数向上缩放(乘法)很可能是始终有问题,但向下缩放(除法)并不总是固有地危险,如果开发人员知道除法发生在适当数量的_字节_和 sizeof
值之间。关闭 WarnOnOffsetDividedBySizeOf
将限制警告仅针对乘法情况。
此情况还会检查指针算术运算中可疑的 alignof
和 offsetof
使用情况,因为两者都返回以字节为单位的“大小”,而不是元素,这可能导致双重缩放的偏移量。
void printEveryEvenIndexElement(int *Array, size_t N) {
int *P = Array;
while (P <= Array + N * sizeof(int)) { // Suspicious pointer arithmetic using sizeof()!
printf("%d ", *P);
P += 2 * sizeof(int); // Suspicious pointer arithmetic using sizeof()!
}
}
struct Message { /* ... */; char Flags[8]; };
void clearFlags(Message *Array, size_t N) {
const Message *End = Array + N;
while (Array < End) {
memset(Array + offsetof(Message, Flags), // Suspicious pointer arithmetic using offsetof()!
0, sizeof(Message::Flags));
++Array;
}
}
对于此检查的虚假模式,cert-arr39-c 将其作为此检查的别名重定向到这里。
此检查对应于 CERT C 编码标准规则 ARR39-C. 不要将缩放的整数加到或减去指针。
Limitations¶
指向类型大小为 1 个字节(例如,最重要的是,char
)的情况将被排除在外。
Options¶
- WarnOnSizeOfConstant¶
当为 true 时,检查将针对类似
sizeof(CONSTANT)
的表达式发出警告。默认值为 true。
- WarnOnSizeOfIntegerExpression¶
当为 true 时,检查将针对类似
sizeof(expr)
的表达式发出警告,其中表达式会生成一个整数。默认值为 false。
- WarnOnSizeOfThis¶
当为 true 时,检查将针对类似
sizeof(this)
的表达式发出警告。默认值为 true。
- WarnOnSizeOfCompareToConstant¶
当为 true 时,检查将针对类似
sizeof(expr) <= k
的表达式发出警告,其中 k 是可疑常量,而 k 为 0 或大于 0x8000。默认值为 true。
- WarnOnSizeOfPointerToAggregate¶
当为 true 时,检查将在
sizeof
的参数是以下情况之一时发出警告:指向聚合类型的指针、返回指向聚合类型的指针值的表达式,或返回从数组到指针转换(可能是隐式或显式,例如array + 2
或(int *)array
)的指针的表达式。默认值为 true。
- WarnOnSizeOfPointer¶
当为 true 时,检查将报告所有表达式,其中
sizeof
的参数是生成指针的表达式(除了可能有意且正确的几个习惯用法表达式)。这会检测 CWE 467 的出现。默认值为 false。
- WarnOnOffsetDividedBySizeOf¶
当设置为 true 时,检查将在指针运算中发出警告,其中元素数量是从除以
sizeof(...)
获得的,例如Ptr + Bytes / sizeof(*T)
。默认值为 true。