The sizeof operator doesn't evaluate its argument, it only looks at the type of its operand.
Let's say you have an array a with type "array [N] of type T". Then, in most cases, the type of the name a is "pointer to T" (T *), and the value of the pointer is the address of the first element of the array (&a[0]). That is, the name of an array "decays" to a pointer to its first element. The "decaying" doesn't happen in the following cases:
- when
a is used with the address-of (&) operator,
- in the initialization of
a (it is illegal to assign to arrays in C), and
- when
a is the operand of the sizeof operator.
So, sizeof a gives you N times sizeof(T).
When you do sizeof(a-3), the type of the operand to sizeof is determined by the expression a-3. Since a in a-3 is used in a value context (i.e., none of the three contexts above), its type is "pointer to int", and the name a decays to a pointer to a[0]. As such, calculating a-3 is undefined behavior, but since sizeof doesn't evaluate its argument, a-3 is used only to determine the type of the operand, so the code is OK (see the first link above for more).
From the above, sizeof(a-3) is equivalent to sizeof(int *), which is 4 on your computer.
The "conversion" is due to the subtraction operator. You can see a similar, and perhaps more surprising, result with the comma operator:
printf("%zu\n", sizeof(1, a));
will also print sizeof(int *), because of the comma operator resulting in a getting used in a value context.