bugprone-sizeof-expression

The check finds usages of sizeof expressions which are most likely errors.

The sizeof operator yields the size (in bytes) of its operand, which may be an expression or the parenthesized name of a type. Misuse of this operator may be leading to errors and possible software vulnerabilities.

Suspicious usage of ‘sizeof(K)’

A common mistake is to query the sizeof of an integer literal. This is equivalent to query the size of its type (probably int). The intent of the programmer was probably to simply get the integer and not its size.

#define BUFLEN 42
char buf[BUFLEN];
memset(buf, 0, sizeof(BUFLEN));  // sizeof(42) ==> sizeof(int)

Suspicious usage of ‘sizeof(expr)’

In cases, where there is an enum or integer to represent a type, a common mistake is to query the sizeof on the integer or enum that represents the type that should be used by sizeof. This results in the size of the integer and not of the type the integer represents:

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 keyword is evaluated to a pointer to an object of a given type. The expression sizeof(this) is returning the size of a pointer. The programmer most likely wanted the size of the object and not the size of the pointer.

class Point {
  [...]
  size_t size() { return sizeof(this); }  // should probably be sizeof(*this)
  [...]
};

Suspicious usage of ‘sizeof(char*)’

There is a subtle difference between declaring a string literal with char* A = "" and char A[] = "". The first case has the type char* instead of the aggregate type char[]. Using sizeof on an object declared with char* type is returning the size of a pointer instead of the number of characters (bytes) in the string literal.

const char* kMessage = "Hello World!";      // const char kMessage[] = "...";
void getMessage(char* buf) {
  memcpy(buf, kMessage, sizeof(kMessage));  // sizeof(char*)
}

Suspicious usage of ‘sizeof(A*)’

A common mistake is to compute the size of a pointer instead of its pointee. These cases may occur because of explicit cast or implicit conversion.

int A[10];
memset(A, 0, sizeof(A + 0));

struct Point point;
memset(point, 0, sizeof(&point));

Suspicious usage of ‘sizeof(…)/sizeof(…)’

Dividing sizeof expressions is typically used to retrieve the number of elements of an aggregate. This check warns on incompatible or suspicious cases.

In the following example, the entity has 10-bytes and is incompatible with the type int which has 4 bytes.

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]
}

In the following example, the expression sizeof(Values) is returning the size of char*. One can easily be fooled by its declaration, but in parameter declaration the size ‘10’ is ignored and the function is receiving a 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

Multiplying sizeof expressions typically makes no sense and is probably a logic error. In the following example, the programmer used * instead of /.

const char kMessage[] = "Hello World!";
void getMessage(char* buf) {
  memcpy(buf, kMessage, sizeof(kMessage) * sizeof(char));  //  sizeof(kMessage) / sizeof(char)
}

This check may trigger on code using the arraysize macro. The following code is working correctly but should be simplified by using only the sizeof operator.

extern Object objects[100];
void InitializeObjects() {
  memset(objects, 0, arraysize(objects) * sizeof(Object));  // sizeof(objects)
}

Suspicious usage of ‘sizeof(sizeof(…))’

Getting the sizeof of a sizeof makes no sense and is typically an error hidden through macros.

#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

Arithmetic operators on pointers automatically scale the result with the size of the pointed typed. Further use of sizeof around pointer arithmetic will typically result in an unintended result.

Scaling the result of pointer difference

Subtracting two pointers results in an integer expression (of type ptrdiff_t) which expresses the distance between the two pointed objects in “number of objects between”. A common mistake is to think that the result is “number of bytes between”, and scale the difference with sizeof, such as P1 - P2 == N * sizeof(T) (instead of P1 - P2 == N) or (P1 - P2) / sizeof(T) instead of 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

Conversely, when performing pointer arithmetics to add or subtract from a pointer, the arithmetic operator implicitly scales the value actually added to the pointer with the size of the pointee, as Ptr + N expects N to be “number of objects to step”, and not “number of bytes to step”.

Seeing the calculation of a pointer where sizeof appears is suspicious, and the result is typically unintended, often out of bounds. Ptr + sizeof(T) will offset the pointer by sizeof(T) elements, effectively exponentiating the scaling factor to the power of 2.

Similarly, multiplying or dividing a numeric value with the sizeof of an element or the whole buffer is suspicious, because the dimensional connection between the numeric value and the actual sizeof result can not always be deduced. While scaling an integer up (multiplying) with sizeof is likely always an issue, a scaling down (division) is not always inherently dangerous, in case the developer is aware that the division happens between an appropriate number of _bytes_ and a sizeof value. Turning WarnOnOffsetDividedBySizeOf off will restrict the warnings to the multiplication case.

This case also checks suspicious alignof and offsetof usages in pointer arithmetic, as both return the “size” in bytes and not elements, potentially resulting in doubly-scaled offsets.

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;
  }
}

For this checked bogus pattern, cert-arr39-c redirects here as an alias of this check.

This check corresponds to the CERT C Coding Standard rule ARR39-C. Do not add or subtract a scaled integer to a pointer.

Limitations

Cases where the pointee type has a size of 1 byte (such as, and most importantly, char) are excluded.

Options

WarnOnSizeOfConstant

When true, the check will warn on an expression like sizeof(CONSTANT). Default is true.

WarnOnSizeOfIntegerExpression

When true, the check will warn on an expression like sizeof(expr) where the expression results in an integer. Default is false.

WarnOnSizeOfThis

When true, the check will warn on an expression like sizeof(this). Default is true.

WarnOnSizeOfCompareToConstant

When true, the check will warn on an expression like sizeof(expr) <= k for a suspicious constant k while k is 0 or greater than 0x8000. Default is true.

WarnOnSizeOfPointerToAggregate

When true, the check will warn when the argument of sizeof is either a pointer-to-aggregate type, an expression returning a pointer-to-aggregate value or an expression that returns a pointer from an array-to-pointer conversion (that may be implicit or explicit, for example array + 2 or (int *)array). Default is true.

WarnOnSizeOfPointer

When true, the check will report all expressions where the argument of sizeof is an expression that produces a pointer (except for a few idiomatic expressions that are probably intentional and correct). This detects occurrences of CWE 467. Default is false.

WarnOnOffsetDividedBySizeOf

When true, the check will warn on pointer arithmetic where the element count is obtained from a division with sizeof(...), e.g., Ptr + Bytes / sizeof(*T). Default is true.