sizeof(sizeof(...))

Does anyone have any insights on the semantics of expressions of the form sizeof(sizeof(...))? For example, the following is legal code:

int baz(int x) {
  typedef int a[f()];
  return sizeof (sizeof(a[bar(x)]));
         + sizeof(sizeof(x));
}

From the C99 standard (6.5.3.4):

   "The sizeof operator yields the size (in bytes) of its operand, which may be an
   expression or the parenthesized name of a type. The size is determined from the type of
   the operand. The result is an integer. Ifthe type of the operand is a variable length array
   type, the operand is evaluated; otherwise, the operand is not evaluated and the result is an
   integer constant."

I am having a little difficulty interpreting the (general) semantics of applying sizeof to sizeof. Moreover, "sizeof (sizeof(a[bar(x)]))" does not cause bar() to be called, whereas (as expected) sizeof(a[bar(x)]) does.

After thinking about this a little more, my understanding is that sizeof simply evaluates to a type of unsigned int, so sizeof(sizeof(...)) is equivalent to sizeof(unsigned int). If anyone thinks this is wrong, please let me know.

Well, the standard says to "integer" type, so probably not unsigned.
But that's my understanding of it. Though I don't believe that sizeof
should be evaluating the expression...

-bw

Well, the standard says to "integer" type, so probably not unsigned.
But that's my understanding of it. Though I don't believe that sizeof
should be evaluating the expression...

Indeed, my understanding was that sizeof expr merely evaluated the type of the expression, not the expression itself.

--Oliver

There are occasions when sizeof(type) actually does evaluate an expression in the case of VLA types. This is what prompted this email (I'm trying to piece together all the contingencies). In clang, the issue is that when the argument type of SizeOfAlignOfTypeExpr is of type VariableArrayType the size expression of the VLA type must be evaluated (within the sizeof) in order to determine the size of the type. I believe this excludes typedefs, since the size expression will be evaluated at the typedef location. There may be other corner cases I'm not seeing yet with sizeof and VLAs.

Not always with VLAs. From the C99 standard (6.5.3.4):

The sizeof operator yields the size (in bytes) of its operand, which may be an
expression or the parenthesized name of a type. The size is determined from the type of
the operand. The result is an integer. If the type of the operand is a variable length array
type, the operand is evaluated; otherwise, the operand is not evaluated and the result is an
integer constant.

Egad, so a = sizeof(char[(int)sqrt(x)]); would be valid? that scares me :open_mouth:
is this valid:

char[n*m] foo(int n, int m) { ... }
...
a=sizeof(foo(x,y))
?

I can't remember the rules for VLAs so i don't know if you can return a VLA :-/

--Oliver

The sizeof operator yields the size (in bytes) of its operand, which may be an
expression or the parenthesized name of a type. The size is determined from the type of
the operand. The result is an integer. If the type of the operand is a variable length array
type, the operand is evaluated; otherwise, the operand is not evaluated and the result is an
integer constant.

Egad, so a = sizeof(char[(int)sqrt(x)]); would be valid? that scares me :open_mouth:

Yes this is valid. It scares me too.

is this valid:

char[n*m] foo(int n, int m) { ... }
...
a=sizeof(foo(x,y))
?

This (I believe) is not valid, and I couldn't get gcc to compile similar code. From the standard: "All declarations of variably modified (VM) types have to be either block scope or function prototype scope." I believe the function prototype scope only encompasses the parameters of the function and not the return type.

Here is some more stuff from the standard on the topic:

0 EXAMPLE 4 All declarations of variably modified (VM) types have to be at either block scope or function prototype scope. Array objects declared with the static or externstorage-class specifier cannot have a variable length array (VLA) type. However, an object declared with the static storage- class specifier can have a VMtype (that is, a pointer to a VLA type). Finally, all identifiers declared with a VM type have to be ordinary identifiers and cannot, therefore, be members of structures or unions.

extern int n;
int A[n]; //invalid: file scope VLA
extern int (*p2)[n]; //invalid: file scope VM
int B[100]; //valid: file scope but not VM

void fvla(int m, int C[m][m]); //valid: VLA with prototype scope

void fvla(int m, int C[m][m]) //valid: adjusted to auto pointer to VLA
{
   typedef int VLA[m][m]; //valid: blockscope typedef VLA
   struct tag {
     int (*y)[n]; //invalid:ynot ordinary identifier
     int z[n]; //invalid:znot ordinary identifier
   };

   int D[m]; //valid: auto VLA

   static int E[m]; //invalid: static blockscope VLA

   extern int F[m]; //invalid: F has linkage and is VLA

   int (*s)[m]; //valid: auto pointer to VLA

   extern int (*r)[m]; //invalid:rhas linkage and points to VLA

   static int (*q)[m] = &B; //valid:qis a static blockpointer to VLA
}

I don't get the reason why someone would do that, but afaict sizeof is a
size_t (not an unsigned int) so sizeof(sizeof(a[bar(x)])) is just
sizeof(size_t). I don't have my C norm at hand, but at least gcc
believes that sizeof "returns" a size_t since its printf format checker
complains if I use "%d" and definitely wants "%zd".

Right. IIRC, size_t can be target specific. On Mac OS X (x86), clang reports the type of sizeof() as being unsigned long (I believe I may have previously said unsigned int).

Ted Kremenek wrote:-

> is this valid:
>
> char[n*m] foo(int n, int m) { ... }
> ...
> a=sizeof(foo(x,y))
> ?

This (I believe) is not valid, and I couldn't get gcc to compile
similar code. From the standard: "All declarations of variably
modi?ed (VM) types have to be either block scope or function
prototype scope."

It's invalid for a different reason (and one clang doesn't
implement, ahem): the return type of a function declarator
cannot have array type.

Neil.

You're understanding is incomplete. The quote from the standard below where it says it is evaluated, means, just that, it is evaluated, honest.

As you suspected the argument isn't evaluated because the type of the argument isn't a variable length array. Put on your recursion hat and just think about the first bit of the operand....

sizeof (...) has type size_t.

size_t isn't a VLA.

no need to consider anything instead of the (...).

For fun, try the follow on the old gray matter:

struct { char o[n][m]; } foo(int n, int m) { }

int main() {
   return sizeof(foo(3,4));
}

:slight_smile:

I found 6.5.3.4.4 to be very clear about this all:

"The value of the result is implementation-defined, and its type (an unsigned
integer type) is size_t, defined in <stddef.h> (and other headers)."

7.17 Common definitions <stddef.h>

The following types and macros are defined in the standard header <stddef.h>.
Some are also defined in other headers, as noted in their respective
subclauses.

The types are [...]

  size_t

which is the unsigned integer type of the result of the sizeof operator; [...]

And 6.5.3.4 from the Rationale is even more specific:

6.5.3.4 The sizeof operator

It is fundamental to the correct usage of functions such as malloc and fread
that sizeof(char) be exactly one. In practice, this means that a byte in C
terms is the smallest unit of storage, even if this unit is 36 bits wide; and
all objects are composed of an integer number of these smallest units. Also
applies if memory is bit addressable.

C89, like K&R, defined the result of the sizeof operator to be a constant of
an unsigned integer type. Common implementations, and common usage, have often
assumed that the resulting type is int. Old code that depends on this behavior
has never been portable to implementations that define the result to be a type
other than int. The C89 Committee did not feel it was proper to change the
language to protect incorrect code.

The type of sizeof, whatever it is, is published (in the library header
<stddef.h>) as size_t, since it is useful for the programmer to be able to
refer to this type. This requirement implicitly restricts size_t to be a
synonym for an existing unsigned integer type. Note also that, although size_t
is an unsigned type, sizeof does not involve any arithmetic operations or
conversions that would result in modulus behavior if the size is too large to
represent as a size_t, thus quashing any notion that the largest declarable
object might be too big to span even with an unsigned long in C89 or uintmax_t
in C99. This also restricts the maximum number of elements that may be
declared in an array, since for any array a of N elements,

  N == sizeof(a)/sizeof(a[0])

Thus size_t is also a convenient type for array sizes, and is so used in
several library functions.

C89 specified that sizeof’s operand can be any value except a bit-field, a
void expression, or a function designator. This generality allows for
interesting environmental inquiries. Given the declarations

  int *p, *q;

these expressions determine the size of the type used for:

  sizeof(F(x)) // ... F’s return value
  sizeof(p-q) // ... pointer difference

(The last type is available as ptrdiff_t in <stddef.h>.)

With the addition of variable length arrays (§6.7.5.2) in C99, the sizeof
operator is a constant expression only if the type of the operand is not a
variable length array type. However, the notion of “size” is consistently
maintained for important operations such as pointer increment, subscripting,
and pointer difference. That is, it is still possible to determine the number
of elements in a variable length array with

  sizeof(vla) / sizeof(vla[0])

Finally, sizeof can still be used in an argument to the malloc function.

QUIET CHANGE IN C99
With the introduction of the long long and extended integer types, the sizeof
operator may yield a value that exceeds the range of an unsigned long.

As IEEE Std 1003.1, 2004 further adds:

The implementation shall support one or more programming environments in which
the widths of ptrdiff_t, size_t, and wchar_t are no greater than the width of
type long. The names of these programming environments can be obtained using
the confstr() function or the getconf utility.

C99 with TC2 further adds for 7.17:

Recommended practice

The types used for size_t and ptrdiff_t should not have an integer conversion
rank greater than that of signed long int unless the implementation supports
objects large enough to make this necessary.

Mike Stump wrote:-

For fun, try the follow on the old gray matter:

struct { char o[n][m]; } foo(int n, int m) { }

int main() {
   return sizeof(foo(3,4));
}

:slight_smile:

"/tmp/bug.c", line 1: error: identifier "n" is not defined
struct { char o[n][m]; } foo(int n, int m) { }
                ^
"/tmp/bug.c", line 1: error: identifier "m" is not defined
struct { char o[n][m]; } foo(int n, int m) { }
                   ^
Seems pretty useless :slight_smile:

Neil.

Does now:
http://lists.cs.uiuc.edu/pipermail/cfe-commits/Week-of-Mon-20071217/003489.html

Thanks,

-Chris