Many people would find it surprising to see the assertion fail, because -1 is obviously less than 2. However, the return type of sizeof is size_t, which is an unsigned integer. On 64bit system, it’s probably unsigned long. Then when int is compared with unsigned long, int is converted to unsigned long.

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdlib.h>
#include <assert.h>

#define array_size(x) (sizeof(x)/sizeof(x[0]))

static int arr[] = {1, 2};

int main()
{
int a = -1;
assert(a < array_size(arr));
return 0;
}

The partial usual arithmetic conversion rule is summarized here, see 6.3.1.8 in C11 for the complete specification.

  1. return if same type
  2. otherwise, if signness is the same, smaller integer is upcasted to larger one
  3. otherwise, if unsigned operand has larger or equal integer, signed integer is converted to unsigned
  4. otherwise, if sign type can represent all values of the unsigned type, unsigned integer is converted to signed, while preserving the value
  5. otherwise, both operands are converted to the unsigned version of the signed operand

In the code snippet, rule 3 is applied, for unsigned long is larger, and -1 is super larger when interpreted as unsigned, hence the assertion fails.

Now we understand this counterintuitive behavior, but having to struggle with it while programming in C is unbearable. Fortunately, turning on warning flags would expose this kind of madness.