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(…))’

获取 sizeofsizeof 毫无意义,通常是通过宏隐藏的错误。

#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 将限制警告仅针对乘法情况。

此情况还会检查指针算术运算中可疑的 alignofoffsetof 使用情况,因为两者都返回以字节为单位的“大小”,而不是元素,这可能导致双重缩放的偏移量。

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 是可疑常量,而 k0 或大于 0x8000。默认值为 true

WarnOnSizeOfPointerToAggregate

当为 true 时,检查将在 sizeof 的参数是以下情况之一时发出警告:指向聚合类型的指针、返回指向聚合类型的指针值的表达式,或返回从数组到指针转换(可能是隐式或显式,例如 array + 2(int *)array)的指针的表达式。默认值为 true

WarnOnSizeOfPointer

当为 true 时,检查将报告所有表达式,其中 sizeof 的参数是生成指针的表达式(除了可能有意且正确的几个习惯用法表达式)。这会检测 CWE 467 的出现。默认值为 false

WarnOnOffsetDividedBySizeOf

当设置为 true 时,检查将在指针运算中发出警告,其中元素数量是从除以 sizeof(...) 获得的,例如 Ptr + Bytes / sizeof(*T)。默认值为 true