How can I print the result of sizeof() at compile time in C?
Asked Answered
S

16

97

For now, I am using a static assert (home brewed based on other web resources) to compare the sizeof() result to various constants. While this works, it is far from elegant or fast.

I can also create an instance of the variable/struct and look in the map file, but this is also less elegant and fast than a direct call/command/operator.

Further, this is an embedded project using multiple cross-compilers, so building and loading a sample program to the target and then reading out a value is even more of a hassle than either of the above.

In my case (old GCC), #warning sizeof(MyStruct) does not actually interpret sizeof() before printing the warning.

Seneschal answered 7/1, 2014 at 18:57 Comment(3)
What is the motivation?Outlandish
Adjacently related: How do I show the value of a #define at compile-time?Chatelaine
For C++, see also: Is it possible to print out the size of a C++ class at compile-time?Chatelaine
A
129

Is it possible to print out the size of a C++ class at compile-time? gave me the idea for this:

char (*__kaboom)[sizeof( YourTypeHere )] = 1;

Which results in the following warning in VS2015:

warning C4047: 'initializing': 'DWORD (*)[88]' differs in levels of indirection from 'int'

Where 88 in this case would be the size you're looking for.

Super hacky, but it works out of the box for clang 3.6

The only trick I could get to work for GCC was abusing -Wformat and having the macro define a function like the following:

void kaboom_print( void )
{
    printf( "%d", __kaboom );
}

Which will give you a warning like:

[...] argument 2 has type 'char (*)[88]'

Ambrosia answered 8/2, 2016 at 2:42 Comment(0)
M
28

Duplicate case constant is a trick that is guaranteed to work IN ALL C COMPILERS regardless of how each of them reports error. For Visual C++, it is simple:

struct X {
    int a,b;
    int c[10];
};
int _tmain(int argc, _TCHAR* argv[])
{
    int dummy;

    switch (dummy) {
    case sizeof(X):
    case sizeof(X):
        break;
    }
    return 0;
}

Compilation result:

 ------ Build started: Project: cpptest, Configuration: Debug Win32 ------
 cpptest.cpp c:\work\cpptest\cpptest\cpptest.cpp(29): error C2196: case value '48' already used
 ========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========

So sizeof the struct X is 48

EDITED (3jun2020): For gcc or any other compilers that only print "duplicate case value", I use this trick to narrow down the value:

1) add a case value 1==2 (to represent false)

2) by trial and error, narrow down the value e.g. I try to guess the sizeof(X) is >16:

#include <stdio.h>
typedef struct _X {
    int a;
    char b[10];
} X;
int main()
{
    printf("Hello World");

    int dummy=0   ;
    switch (dummy) {
    case  1==2:
    case sizeof( X)>16:
    //case 16:
    break;
    }
    return 0;
}

result:

main.c: In function ‘main’:
main.c:14:5: error: duplicate case value
     case sizeof( X)>16:
     ^~~~
main.c:13:5: error: previously used here
     case  1==2:

so it is false, i.e. sizeof(X)<=16.

3) repeat with some other sensible values. e.g. try to guess that it is 16 i.e. sizeof(X)==16. If it doesn't complain about duplicate case value. Then the expression is true.

4) optionally add a case 16 to verify it e.g.

#include <stdio.h>
typedef struct _X {
    int a;
    char b[10];
} X;
int main()
{
    printf("Hello World");

    int dummy=0   ;
    switch (dummy) {
   // case  1==2:
    case sizeof( X):
    case 16:
    break;
    }
    return 0;
}

result

main.c: In function ‘main’:
main.c:15:5: error: duplicate case value
     case 16:
     ^~~~
main.c:14:5: error: previously used here
     case sizeof( X):

confirming that sizeof(X) is 16.

Alternatively, it is observed that gcc can report multiple duplicates, so this trick is possible for making multiple guesses on a single pass:

#include <stdio.h>
typedef struct _X {
    int a;
    char b[10];
} X;
int main()
{
    printf("Hello World");

    int dummy=0   ;
    switch (dummy) {
    case  1==2: //represents false
    case 1==1: //represents true
    case sizeof( X)>10:
    case sizeof( X)>12:
    case sizeof( X)>14:
    case sizeof( X)>16:
    case  sizeof( X)==16:
    //case 16:
    break;
    }
    return 0;
}

result

main.c: In function ‘main’:
main.c:14:5: error: duplicate case value
     case sizeof( X)>10:
     ^~~~
main.c:13:5: error: previously used here
     case 1==1:
     ^~~~
main.c:15:5: error: duplicate case value
     case sizeof( X)>12:
     ^~~~
main.c:13:5: error: previously used here
     case 1==1:
     ^~~~
main.c:16:5: error: duplicate case value
     case sizeof( X)>14:
     ^~~~
main.c:13:5: error: previously used here
     case 1==1:
     ^~~~
main.c:17:5: error: duplicate case value
     case sizeof( X)>16:
     ^~~~
main.c:12:5: error: previously used here
     case  1==2:
     ^~~~
main.c:18:5: error: duplicate case value
     case  sizeof( X)==16:
     ^~~~
main.c:13:5: error: previously used here
     case 1==1:
     ^~~~

suggesting the sizeof(X) is >10, >12, >14 but is not >16. The ==16 is added as a final guess.

Monohydroxy answered 9/5, 2016 at 10:34 Comment(0)
E
18

The following way, which works in GCC, Clang, MSVC and more, even in older versions, is based on failed conversion of a function parameter from pointer to array to a scalar type. The compilers do print size of the array, so you can get the value from the output. Works both in C and C++ mode.

Example code to find out sizeof(long) (play with it online):

char checker(int);
char checkSizeOfInt[sizeof(long)]={checker(&checkSizeOfInt)};

Examples of relevant output:

  • GCC 4.4.7

<source>:1: note: expected 'int' but argument is of type 'char (*)[8]'

  • clang 3.0.0

<source>:1:6: note: candidate function not viable: no known conversion from 'char (*)[8]' to 'int' for 1st argument;

  • MSVC 19.14

<source>(2): warning C4047: 'function': 'int' differs in levels of indirection from 'char (*)[4]'

Eudora answered 21/12, 2018 at 12:19 Comment(0)
K
10

One more way (that actually works):

char __foo[sizeof(MyStruct) + 1] = {[sizeof(MyStruct)] = ""};

Works with old'ish gcc 5.x. Yields an error like this:

a.c:8:54: error: initializer element is not computable at load time
a.c:8:54: note: (near initialization for 'a[8]')

p.s. obviously, this one is (very) gcc specific. All other methods weren't working for me.

Klaxon answered 13/12, 2018 at 10:15 Comment(1)
You don't even have to specify the size for the array: char __foo[] = {[sizeof(MyStruct)] = ""};Acierate
M
4

@jws nice idea!. Howewer, sizeof(xxx) is a constant expression (except VLA, https://en.cppreference.com/w/c/language/sizeof), so the sizeof operator should work even in the case selection:

enum e1 {dummy=-1};
enum e1 ev;
switch (ev) {
    case sizeof(myType):;
    break;
    default:;
}

.. it work in my GCC: "..\WinThreads.c:18:9: warning: case value '4' not in enumerated type 'enum e1' [-Wswitch] "

Minicam answered 12/3, 2019 at 19:17 Comment(0)
T
4

Quick and simple solution that worked for me (GCC):

(char[sizeof(long long)])"bla";

This results in an error message that reveals the size of long long:

ISO C++ forbids casting to an array type 'char [8]'
Troublous answered 20/11, 2020 at 20:59 Comment(1)
Note, this actually compiled for me in VS 2019. But changing "bla" to a number (e.g. 4) worked.Mishmash
P
3

I stumbled upon a solution similar to Bakhazard's great solution, and this one produces a much less verbose warning, so you may find it useful:

char (*__fail)(void)[sizeof(uint64_t)] = 1;

This produces the error message

Function cannot return array type 'char [8]'

This was tested with the latest version of clang(1).

Pforzheim answered 5/1, 2018 at 1:7 Comment(0)
P
2

Edited in 2024.04.11

Using c++11's static_assert will make it easy. Example:

struct MyStruct
{
    int a;
    double b;
    char c;
};

int main()
{
    static_assert(sizeof(MyStruct) == 0, "...");
    return 0;
}

Compile it with GCC 13.2 (https://godbolt.org/z/eWqTxxTdP), the output:

<source>: In function 'int main()':
<source>:10:36: error: static assertion failed: ...
   10 |     static_assert(sizeof(MyStruct) == 0, "...");
      |                   ~~~~~~~~~~~~~~~~~^~~~
<source>:10:36: note: the comparison reduces to '(24 == 0)'

Among which, 24 == 0 gives the size, 24.


Original answer

//main.cpp
#include <cstddef>
template <std::size_t x>
struct show_size;

void foo()
{
    show_size<sizeof(my_type)>();//!!please change `my_type` to your expected
}

int main()
{
    return 0;
}

You can compile this pretty simple code, and during its pre-compilation stage, the compiler will give error, in which the sizeof(my_type) will give concrete value. e.g.:

g++ main.cpp
Parfait answered 29/7, 2020 at 6:28 Comment(0)
E
2

A much easier way is to do this using gdb. If you're able to compile your .elf with debugging symbols.

Example:

In my test.c file, I declare

typedef struct {
  int a;
  int b;
  char d;
  long e[10];
} my_struct_t;

I compile it using gcc

gcc test.c -o app.elf -g

I run

gdb app.elf

And without running the executable, you can do

gdb app.elf
(gdb) ptype /o my_struct_t

type = struct {
/*    0      |     4 */    int a;
/*    4      |     4 */    int b;
/*    8      |     1 */    char d;
/* XXX  7-byte hole  */
/*   16      |    80 */    long e[10];

/* total size (bytes):   96 */
}

You can also print the result of the sizeof function in gdb

(gdb) p sizeof(my_struct_t)
$1 = 96

Since you don't need to run the executable, The .elf can even be the product of a cross-compilation (as long as you use your toolchain's gdb or gdb-multiarch).

Eyre answered 1/9, 2022 at 9:40 Comment(0)
C
2

I didn't find a single answer here that suited my needs, so I put in the effort to write my own, and here's what I've come up with.

Printing the size of (sizeof()) a type or variable in an error message at compile time in both C and C++

The following techniques work in both C and C++ and have been tested with the GCC compiler gcc --version gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0. They should also work with the LLVM Clang compiler, since it is designed to be gcc-compatible by design (see: https://clang.llvm.org/: "GCC & MSVC compatibility"), but I have not tested it with clang myself.

Option 1 [preferred]: Some really nice wrapper macros

Here's a quick demo. Use these two macros (just descriptions, not definitions, are shown here):

// Macro to **locally** (meaning: from a local scope) print out the size of a
// type or variable into an error message at compile time. 
#define COMPILE_TIME_PRINT_SIZEOF_LOCAL(variable_or_data_type)

// Macro to **globally** (meaning: from a global scope) print out the size of a
// type or variable into an error message at compile time. 
#define COMPILE_TIME_PRINT_SIZEOF_GLOBAL(variable_or_data_type)

Demo:

sizeof_compile_time_lib_test_BEST.c from my eRCaGuy_hello_world repo:

#include "sizeof_compile_time_lib.h"

#include <stdbool.h> // For `true` (`1`) and `false` (`0`) macros in C
#include <stdint.h>  // For `uint8_t`, `int8_t`, etc.
#include <stdio.h>   // For `printf()`


typedef struct My_struct_s
{
              // For my X86-64 Linux machine:
    bool b;   // 1 byte + 3 padding bytes
    int i;    // 4 bytes
    float f;  // 4 bytes
    char c;   // 1 byte + 3 padding bytes
    double d; // 8 bytes
} My_struct;  // 24 bytes total

struct My_struct2_s
{
              // For my X86-64 Linux machine:
    bool b;   // 1 byte + 3 padding bytes
    int i;    // 4 bytes
};            // 8 bytes total

COMPILE_TIME_PRINT_SIZEOF_GLOBAL(My_struct);                // 24
COMPILE_TIME_PRINT_SIZEOF_GLOBAL(struct My_struct2_s);      // 8

int main()
{
    printf("Testing 'sizeof_compile_time_lib.h'.\n\n");

    My_struct my_structs[10];
    COMPILE_TIME_PRINT_SIZEOF_LOCAL(My_struct);             // 24
    COMPILE_TIME_PRINT_SIZEOF_LOCAL(struct My_struct2_s);   // 8
    COMPILE_TIME_PRINT_SIZEOF_LOCAL(my_structs);            // 240

    return 0;
}

Here is the sample output in C. Notice that the ‘compile_time_sizeof__line_72’ and This_is_the_size_of_your_type_on__line_72 parts show the line number of where the macro was called from! And the size of the type or variable is printed in this part of each error message: error: case value ‘24’ not in enumerated type ‘enum This_is_the_size_of_your_type_on__line_72’ [-Werror=switch]. It's really quite beautiful. Scroll to the far right here to see that the sizes printed are from lines 72, 73, 80, 81, and 82, with sizes 24, 8, 24, 8, and 240, respectively:

eRCaGuy_hello_world/c$ gcc -Wall -Wextra -Werror -O3 -std=gnu17 sizeof_compile_time_lib_test_BEST.c -o bin/a -lm && bin/a
In file included from sizeof_compile_time_lib_test_BEST.c:48:
sizeof_compile_time_lib_test_BEST.c: In function ‘compile_time_sizeof__line_72’:
sizeof_compile_time_lib.h:68:13: error: case value ‘24’ not in enumerated type ‘enum This_is_the_size_of_your_type_on__line_72’ [-Werror=switch]
   68 |             case sizeof(variable_or_data_type): \
      |             ^~~~
sizeof_compile_time_lib.h:82:9: note: in expansion of macro ‘COMPILE_TIME_PRINT_SIZEOF_LOCAL’
   82 |         COMPILE_TIME_PRINT_SIZEOF_LOCAL(variable_or_data_type); \
      |         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
sizeof_compile_time_lib_test_BEST.c:72:1: note: in expansion of macro ‘COMPILE_TIME_PRINT_SIZEOF_GLOBAL’
   72 | COMPILE_TIME_PRINT_SIZEOF_GLOBAL(My_struct);
      | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
sizeof_compile_time_lib_test_BEST.c: In function ‘compile_time_sizeof__line_73’:
sizeof_compile_time_lib.h:68:13: error: case value ‘8’ not in enumerated type ‘enum This_is_the_size_of_your_type_on__line_73’ [-Werror=switch]
   68 |             case sizeof(variable_or_data_type): \
      |             ^~~~
sizeof_compile_time_lib.h:82:9: note: in expansion of macro ‘COMPILE_TIME_PRINT_SIZEOF_LOCAL’
   82 |         COMPILE_TIME_PRINT_SIZEOF_LOCAL(variable_or_data_type); \
      |         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
sizeof_compile_time_lib_test_BEST.c:73:1: note: in expansion of macro ‘COMPILE_TIME_PRINT_SIZEOF_GLOBAL’
   73 | COMPILE_TIME_PRINT_SIZEOF_GLOBAL(struct My_struct2_s);
      | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
sizeof_compile_time_lib_test_BEST.c: In function ‘main’:
sizeof_compile_time_lib.h:68:13: error: case value ‘24’ not in enumerated type ‘enum This_is_the_size_of_your_type_on__line_80’ [-Werror=switch]
   68 |             case sizeof(variable_or_data_type): \
      |             ^~~~
sizeof_compile_time_lib_test_BEST.c:80:5: note: in expansion of macro ‘COMPILE_TIME_PRINT_SIZEOF_LOCAL’
   80 |     COMPILE_TIME_PRINT_SIZEOF_LOCAL(My_struct);
      |     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
sizeof_compile_time_lib.h:68:13: error: case value ‘8’ not in enumerated type ‘enum This_is_the_size_of_your_type_on__line_81’ [-Werror=switch]
   68 |             case sizeof(variable_or_data_type): \
      |             ^~~~
sizeof_compile_time_lib_test_BEST.c:81:5: note: in expansion of macro ‘COMPILE_TIME_PRINT_SIZEOF_LOCAL’
   81 |     COMPILE_TIME_PRINT_SIZEOF_LOCAL(struct My_struct2_s);
      |     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
sizeof_compile_time_lib.h:68:13: error: case value ‘240’ not in enumerated type ‘enum This_is_the_size_of_your_type_on__line_82’ [-Werror=switch]
   68 |             case sizeof(variable_or_data_type): \
      |             ^~~~
sizeof_compile_time_lib_test_BEST.c:82:5: note: in expansion of macro ‘COMPILE_TIME_PRINT_SIZEOF_LOCAL’
   82 |     COMPILE_TIME_PRINT_SIZEOF_LOCAL(my_structs);
      |     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
cc1: all warnings being treated as errors

Here is the full header file with the macro definitions:

sizeof_compile_time_lib.h
(Note: a good alternative name for this header file might be CompileTimeSizeof.h):

#pragma once

// See: 
// 1. https://mcmap.net/q/24821/-creating-c-macro-with-and-__line__-token-concatenation-with-positioning-macro
// 2. "eRCaGuy_hello_world/c/macro_make_unique_variable_name_with_line_number.c"
#define CONCAT_(prefix, suffix) prefix##suffix
/// Concatenate `prefix, suffix` into `prefixsuffix`
#define CONCAT(prefix, suffix) CONCAT_(prefix, suffix)
///
/// Make a unique variable name containing the line number at the end of the
/// name. Ex: `uint64_t MAKE_UNIQUE_VARIABLE_NAME(counter) = 0;` would
/// produce `uint64_t counter_7 = 0` if the call is on line 7!
#define MAKE_UNIQUE_VARIABLE_NAME(prefix) CONCAT(prefix##_, __LINE__)

// Macro to **locally** (meaning: from a local scope) print out the size of a
// type or variable into an error message at compile time. 
#define COMPILE_TIME_PRINT_SIZEOF_LOCAL(variable_or_data_type) \
    { \
        /* save the current GCC diagnostic state */ \
        _Pragma("GCC diagnostic push") \
        /* Activate `-Wswitch` switch case warnings, and make them become */ \
        /* errors, so that the enum and switch case below will throw */ \
        /* a compile-time error with the `variable_or_data_type`'s */ \
        /* size printed in it! */ \
        _Pragma("GCC diagnostic error \"-Wswitch\"") \
        enum MAKE_UNIQUE_VARIABLE_NAME(This_is_the_size_of_your_type_on__line) \
        { \
            DUMMY_VAL = 0 \
        } dummy = DUMMY_VAL; \
        switch (dummy) \
        { \
            case DUMMY_VAL: \
                break; \
            case sizeof(variable_or_data_type): \
                break; \
        } \
        /* restore the saved GCC diagnostic state */ \
        _Pragma("GCC diagnostic pop") \
    }

// Macro to **globally** (meaning: from a global scope) print out the size of a
// type or variable into an error message at compile time. 
#define COMPILE_TIME_PRINT_SIZEOF_GLOBAL(variable_or_data_type) \
    /* Make a unique function name for each usage of this macro */ \
    __attribute__((unused)) \
    void MAKE_UNIQUE_VARIABLE_NAME(compile_time_sizeof__line)() \
    { \
        COMPILE_TIME_PRINT_SIZEOF_LOCAL(variable_or_data_type); \
    }

Option 2: the raw enum and switch case

To get the size of the My_struct type or the my_structs array variable at compile time:

  1. Outside a function, use this:

    // Ex. 1: Globally check the size of the `My_struct` type, OR some variable. 
    // Let's check the `sizeof(My_struct)` below:
    #pragma GCC diagnostic push  // save the current GCC diagnostic state
    #pragma GCC diagnostic ignored "-Wunused-function"
    #pragma GCC diagnostic error "-Wswitch"
    // This function is required since this check is outside of all other 
    // functions otherwise.
    void dummy_func() 
    {
        typedef enum Dummy_e
        {
            DUMMY_VAL = 0
        } Dummy;
        Dummy dummy = DUMMY_VAL;
        switch (dummy)
        {
            case DUMMY_VAL:
                break;
            case sizeof(My_struct): // <== THIS CHECKS THE SIZE OF YOUR TYPE
                                    //     OR VARIABLE
                break;
        }
    }
    #pragma GCC diagnostic pop  // restore the saved GCC diagnostic state
    
  2. Inside of a function, use this:

    // Ex. 2: Locally check the size of the `My_struct` type, OR  some variable. 
    // Let's check the `sizeof(my_structs)` array below:
    #pragma GCC diagnostic push  // save the current GCC diagnostic state
    #pragma GCC diagnostic error "-Wswitch"
    {
        typedef enum Dummy_e
        {
            DUMMY_VAL = 0
        } Dummy;
        Dummy dummy = DUMMY_VAL;
        switch (dummy)
        {
            case DUMMY_VAL:
                break;
            case sizeof(my_structs): // <== THIS IS THE CHECK
                break;
        }
    }
    #pragma GCC diagnostic pop  // restore the saved GCC diagnostic state
    

Full code:

sizeof_compile_time__print_sizeof_struct_or_datatype.c:

#include <stdbool.h> // For `bool` type
#include <stdio.h>   // For `printf()`


typedef struct My_struct_s
{
              // For my X86-64 Linux machine:
    bool b;   // 1 byte + 3 padding bytes
    int i;    // 4 bytes
    float f;  // 4 bytes
    char c;   // 1 byte + 3 padding bytes
    double d; // 8 bytes
} My_struct;  // 24 bytes total

// Ex. 1: Globally check the size of the `My_struct` type, OR some variable. 
// Let's check the `sizeof(My_struct)` below:
#pragma GCC diagnostic push  // save the current GCC diagnostic state
#pragma GCC diagnostic ignored "-Wunused-function"
#pragma GCC diagnostic error "-Wswitch"
// This function is required since this check is outside of all other functions
// otherwise.
void dummy_func() 
{
    typedef enum Dummy_e
    {
        DUMMY_VAL = 0
    } Dummy;
    Dummy dummy = DUMMY_VAL;
    switch (dummy)
    {
        case DUMMY_VAL:
            break;
        case sizeof(My_struct): // <== THIS CHECKS THE SIZE OF YOUR TYPE OR VAR
            break;
    }
}
#pragma GCC diagnostic pop  // restore the saved GCC diagnostic state

int main()
{
    printf("Compile-time size checks.\n\n");

    My_struct my_structs[10];

    // Ex. 2: Locally check the size of the `My_struct` type, OR  some variable. 
    // Let's check the `sizeof(my_structs)` array below:
    #pragma GCC diagnostic push  // save the current GCC diagnostic state
    #pragma GCC diagnostic error "-Wswitch"
    {
        typedef enum Dummy_e
        {
            DUMMY_VAL = 0
        } Dummy;
        Dummy dummy = DUMMY_VAL;
        switch (dummy)
        {
            case DUMMY_VAL:
                break;
            case sizeof(my_structs): // <== THIS IS THE CHECK
                break;
        }
    }
    #pragma GCC diagnostic pop  // restore the saved GCC diagnostic state


    return 0;
}

Sample output:

  1. In C:

    eRCaGuy_hello_world/c$ gcc sizeof_compile_time__print_sizeof_struct_or_datatype.c -o bin/a && bin/a
    sizeof_compile_time__print_sizeof_struct_or_datatype.c: In function ‘dummy_func’:
    sizeof_compile_time__print_sizeof_struct_or_datatype.c:80:9: error: case value ‘24’ not in enumerated type ‘Dummy’ {aka ‘enum Dummy_e’} [-Werror=switch]
       80 |         case sizeof(My_struct): // <== THIS CHECKS THE SIZE OF YOUR TYPE OR VAR
          |         ^~~~
    sizeof_compile_time__print_sizeof_struct_or_datatype.c: In function ‘main’:
    sizeof_compile_time__print_sizeof_struct_or_datatype.c:106:13: error: case value ‘240’ not in enumerated type ‘Dummy’ {aka ‘enum Dummy_e’} [-Werror=switch]
      106 |             case sizeof(my_structs): // <== THIS IS THE CHECK
          |             ^~~~
    cc1: some warnings being treated as errors
    
  2. Or, in C++:

    eRCaGuy_hello_world/c$ g++ sizeof_compile_time__print_sizeof_struct_or_datatype.c -o bin/a && bin/a
    sizeof_compile_time__print_sizeof_struct_or_datatype.c: In function ‘void dummy_func()’:
    sizeof_compile_time__print_sizeof_struct_or_datatype.c:80:9: error: case value ‘24’ not in enumerated type ‘Dummy’ {aka ‘dummy_func()::Dummy_e’} [-Werror=switch]
       80 |         case sizeof(My_struct): // <== THIS CHECKS THE SIZE OF YOUR TYPE OR VAR
          |         ^~~~
    sizeof_compile_time__print_sizeof_struct_or_datatype.c: In function ‘int main()’:
    sizeof_compile_time__print_sizeof_struct_or_datatype.c:106:13: error: case value ‘240’ not in enumerated type ‘Dummy’ {aka ‘main()::Dummy_e’} [-Werror=switch]
      106 |             case sizeof(my_structs): // <== THIS IS THE CHECK
          |             ^~~~
    cc1plus: some warnings being treated as errors
    

References

  1. My eRCaGuy_hello_world repo links, already shown above, to my test code and libraries.
  2. My answer: How to auto-generate unique variable names with the line number in them by using macros - my MAKE_UNIQUE_VARIABLE_NAME() macro.
  3. https://gcc.gnu.org/onlinedocs/gcc/Diagnostic-Pragmas.html
  4. Usage of _Pragma("string"):
    1. ***** https://gcc.gnu.org/onlinedocs/cpp/Pragmas.html
    2. https://mcmap.net/q/24837/-difference-between-pragma-and-_pragma-in-c
  5. Doing arithmetic on __LINE__ in the C preprocessor:
    1. https://mcmap.net/q/24838/-count-lines-between-two-code-locations-in-c-preprocessor
    2. https://mcmap.net/q/24839/-is-there-a-way-to-get-the-value-of-__line__-on-one-line-and-use-that-value-on-other-lines
  6. __attribute__((unused)): https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#Common-Function-Attributes
  7. All content above is my own, but I also had some very helpful chats with the GitHub Copilot AI as I figured things out.

See also

  1. Alternatives:
    1. @Lundin has a really clever static-assert-based approach here which is compiler-agnostic.
  2. My answer with a very condensed version of the answer above: Is it possible to print out the size of a C++ class at compile-time?

Adjacently-related

  1. My answer: How do I show the value of a #define at compile-time? / Print expanded macro values at compile-time
Chatelaine answered 12/3 at 5:34 Comment(3)
My, that's a nice answer. You're rather late to the party - let's set this answer on it's way.Merkley
The problem with all solutions posted is that they rely on specific diagnostic messages from specific compilers. I can't think of an elegant way to solve that though, since the only compile-time printing that exists are #error and static_assert, and both of them are macro-unfriendly.Benzaldehyde
And just because of that I went ahead and posted an answer too :) https://mcmap.net/q/24829/-how-can-i-print-the-result-of-sizeof-at-compile-time-in-c The only portable one so far, as far as I can tell.Benzaldehyde
E
1

My gcc C compiler refuses to print the size using any of the above solutions. I inverted the logic to inject compiler warnings for what size it is not.

enum e
{
    X = sizeof(struct mystruct)
};

void foo()
{
    static enum e ev;

    switch (ev)
    {
    case 0:
    case 4:
    case 8:
    case 12:
    case 16:
    case 20:
        break;
    }
}

Then I have to look through the warnings for the missing number.

warning: case value '0' not in enumerated type 'e' [-Wswitch]
warning: case value '4' not in enumerated type 'e' [-Wswitch]
warning: case value '12' not in enumerated type 'e' [-Wswitch]
warning: case value '16' not in enumerated type 'e' [-Wswitch]
warning: case value '20' not in enumerated type 'e' [-Wswitch]

So then my struct size is 8.

My packing is 4.

Meh... it's an option.

Exosphere answered 21/11, 2018 at 1:47 Comment(1)
Gcc complains about unhandled cases in switches. So if you had some invalid entry like case 1: and no default, gcc should complain case 8 not handled.Impetigo
F
1

Though this isn't exactly at compile time, it is before runtime, so it could still be relevant for some people.

You can define an array like so:

uint8_t __some_distinct_name[sizeof(YourTypeHere)];

And then, after compilation, get the size from the object file:

$ nm -td -S your_object_file |       # list symbols and their sizes, in decimal
  grep ' __some_distinct_name$' |    # select the right one
  cut -d' ' -f2 |                    # grab the size field
  xargs printf "Your type is %d B\n" # print
Franconia answered 4/12, 2018 at 20:9 Comment(0)
A
1

GCC wasn't giving any of the results with array indexes in them, so I came up with this:

int a;
a = 1/ (sizeof(struct page5_data) & 0x0001);
a = 1/ (sizeof(struct page5_data) & 0x0002);
a = 1/ (sizeof(struct page5_data) & 0x0004);
a = 1/ (sizeof(struct page5_data) & 0x0008);
a = 1/ (sizeof(struct page5_data) & 0x0010);
a = 1/ (sizeof(struct page5_data) & 0x0020);
a = 1/ (sizeof(struct page5_data) & 0x0040);
a = 1/ (sizeof(struct page5_data) & 0x0080);
a = 1/ (sizeof(struct page5_data) & 0x0100);
a = 1/ (sizeof(struct page5_data) & 0x0200);
a = 1/ (sizeof(struct page5_data) & 0x0400);
a = 1/ (sizeof(struct page5_data) & 0x0800);
a = 1/ (sizeof(struct page5_data) & 0x1000);
a = 1/ (sizeof(struct page5_data) & 0x2000);
a = 1/ (sizeof(struct page5_data) & 0x4000);
a = 1/ (sizeof(struct page5_data) & 0x8000);
(void)a;

Gives:

test.c:115:7: error: division by zero [-Werror=div-by-zero]
  a = 1/ (sizeof(struct page5_data) & 0x0001);
       ^

test.c:116:7: error: division by zero [-Werror=div-by-zero]
  a = 1/ (sizeof(struct page5_data) & 0x0002);
       ^

test.c:125:7: error: division by zero [-Werror=div-by-zero]
  a = 1/ (sizeof(struct page5_data) & 0x0400);
       ^

test.c:126:7: error: division by zero [-Werror=div-by-zero]
  a = 1/ (sizeof(struct page5_data) & 0x0800);
       ^

test.c:127:7: error: division by zero [-Werror=div-by-zero]
  a = 1/ (sizeof(struct page5_data) & 0x1000);
       ^

test.c:128:7: error: division by zero [-Werror=div-by-zero]
  a = 1/ (sizeof(struct page5_data) & 0x2000);
       ^

test.c:129:7: error: division by zero [-Werror=div-by-zero]
  a = 1/ (sizeof(struct page5_data) & 0x4000);
       ^

test.c:130:7: error: division by zero [-Werror=div-by-zero]
  a = 1/ (sizeof(struct page5_data) & 0x8000);
       ^

This tells us the zero bits of a 16bit number, the others must be '1'. So we can determine the value is:

0000 0011 1111 1100 = 0x03fc
Anility answered 26/1, 2023 at 18:27 Comment(0)
B
1

The problem with literally every answer posted here is that they rely on a certain diagnostic message from a certain compiler. Problems:

  • That's compiler-specific and non-portable.
  • Even for a certain compiler, compiler diagnostic messages change from version to version, so it is not reliable.
  • There are no requirements on the compiler to print any particular message. A compiler that prints the text bananas! each time it encounters a constraint or syntax violation of the C language is a fully conforming implementation.
  • And in case whatever produced the diagnostic is not a constraint or syntax violation, diagnostic messages are not required but entirely optional.

The only portable standard ways to "print during compile-time" is to either use #error or static_assert. Both of them are kind of unfriendly to macros.

It would be possible to write something fully portable by just repeating a bunch of static asserts:

#define PRINT_SIZE(t)                         \
do {                                          \
  static_assert(sizeof(t)!=1, #t ": size 1"); \
  static_assert(sizeof(t)!=2, #t ": size 2"); \
  static_assert(sizeof(t)!=3, #t ": size 3"); \
  static_assert(sizeof(t)!=4, #t ": size 4"); \
  static_assert(sizeof(t)!=5, #t ": size 5"); \
  static_assert(sizeof(t)!=6, #t ": size 6"); \
  static_assert(sizeof(t)!=7, #t ": size 7"); \
  static_assert(sizeof(t)!=8, #t ": size 8"); \
  static_assert(sizeof(t)!=9, #t ": size 9"); \
} while(0)

Advantages:

  • Works on all C compilers!
  • Pure standard C (C11 to C23).
  • Works on both types and variables - anything you can pass to sizeof().
  • Simple.

Disadvantages:

  • You have to type out a long list of static asserts.

    But if you happen to know a programmer (I know a few), they can probably cook up a script/program generating n text lines in a file sizeof.c, then execute that script (automatically from the IDE) in order to generate as many lines as you suspect the maximum object size to be.

Full example:

#define PRINT_SIZE(t)                         \
do {                                          \
  static_assert(sizeof(t)!=1, #t ": size 1"); \
  static_assert(sizeof(t)!=2, #t ": size 2"); \
  static_assert(sizeof(t)!=3, #t ": size 3"); \
  static_assert(sizeof(t)!=4, #t ": size 4"); \
  static_assert(sizeof(t)!=5, #t ": size 5"); \
  static_assert(sizeof(t)!=6, #t ": size 6"); \
  static_assert(sizeof(t)!=7, #t ": size 7"); \
  static_assert(sizeof(t)!=8, #t ": size 8"); \
  static_assert(sizeof(t)!=9, #t ": size 9"); \
} while(0)

int main (void)
{
  PRINT_SIZE(int);
  double d;
  PRINT_SIZE(d);
  PRINT_SIZE("bananas!");
}

Output clang:

<source>:16:3: error: static assertion failed due to requirement 'sizeof(int) != 4': int: size 4
<source>:18:3: error: static assertion failed due to requirement 'sizeof (d) != 8': d: size 8
<source>:19:3: error: static assertion failed due to requirement 'sizeof ("bananas!") != 9': "bananas!": size 9

Output gcc:

<source>:6:3: error: static assertion failed: "int: size 4"
<source>:10:3: error: static assertion failed: "d: size 8"
<source>:11:3: error: static assertion failed: "\"bananas!\": size 9"
Benzaldehyde answered 12/3 at 15:50 Comment(2)
Upvoted. That's a pretty cool approach. For anyone who needs a static assert to work across language standards, here's what I meticulously came up with for any version of C or C++. As for what a header file library might look like for this PRINT_SIZE() macro, it would certainly be huge. Some sizes of variables on microcontroller code I'm working in are up to 80~250 KiB, and on PC code, some objects could be megabytes in size, which would require millions of lines of static asserts. I'd be interested in seeing how well that works.Chatelaine
@GabrielStaples It would be rather trivial to solve if not for static_assert being a "primary expression" and insisting on a string literal as second parameter and not some form of compile-time expression. Otherwise if it had allowed expressions, one could have generated the string literal through various macro tricks. There might be a way to do so which I haven't thought of.Benzaldehyde
M
-1

This is a generic solution for any C compilers.

I've realized that if our aim is knowing the value of a sizeof() instead of printing out its value, then we just need to evaluate a few compile time sizeof(X)>?? expressions to narrow down the value.

The trick is to produce compile time errors when the expressions evaluate to false(zero) or true (non-zero).

Many standard C constructs can achieve our goal. The duplicate case value trick I described separately is one of them. Another one is through test for division by zero in an initializer, which the compiler evaluates at compile time. For example, to get the size of X:

struct _X {
  int a;
  char c;
  double d;
  float f[30];
} X;

Compile with a few lines:

#include <stdio.h>
struct _X {
  int a;
  char c;
  double d;
  float f[30];
} X;

int r2=1/(sizeof(X)<170);
int r3=1/(sizeof(X)<100);
int r4=1/(sizeof(X)<80);
int r5=1/(sizeof(X)<60);

int main()
{
   return 0;
}

Result:

main.c:17:9: warning: division by zero [-Wdiv-by-zero]
 int r3=1/(sizeof(X)<100);
         ^
main.c:17:8: error: initializer element is not constant
 int r3=1/(sizeof(X)<100);
        ^
main.c:18:9: warning: division by zero [-Wdiv-by-zero]
 int r4=1/(sizeof(X)<80);
         ^
main.c:18:8: error: initializer element is not constant
 int r4=1/(sizeof(X)<80);
        ^
main.c:19:9: warning: division by zero [-Wdiv-by-zero]
 int r5=1/(sizeof(X)<60);
         ^
main.c:19:8: error: initializer element is not constant
 int r5=1/(sizeof(X)<60);
        ^

Implying sizeof(X)<170 is true (non-zero) but sizeof(X)<100 is false (causing division by zero at compile time). Then we can get the actual value by repeating the test with some other values. e.g:

#include <stdio.h>

struct _X {
  int a;
  char c;
  double d;
  float f[30];
} X;

int r2=1/(sizeof(X)<140);
int r3=1/(sizeof(X)<137);
int r4=1/(sizeof(X)<136);
int r5=1/(sizeof(X)!=136);

int main()
{
    return 0;
}

result

main.c:18:9: warning: division by zero [-Wdiv-by-zero]
 int r4=1/(sizeof(X)<136);
         ^
main.c:18:8: error: initializer element is not constant
 int r4=1/(sizeof(X)<136);
        ^
main.c:19:9: warning: division by zero [-Wdiv-by-zero]
 int r5=1/(sizeof(X)!=136);
         ^
main.c:19:8: error: initializer element is not constant
 int r5=1/(sizeof(X)!=136);
        ^

Hence, we know sizeof(X)==136.

Alternatively, by using the ?: operator, we can make use of more C language constructs that are evaluated at compile time. Visual C++ example using array declaration:

#include "stdafx.h"
struct X {
  int a;
  char b[30];
  double d;
  float f[20];
};
int a1[sizeof(X)<130?-1:1];
int a2[sizeof(X)<120?1:-1];
int a3[sizeof(X)==128?-1:1];

int _tmain(int argc, _TCHAR* argv[]){
  return 0;
}

Result:

1>------ Build started: Project: cpptest, Configuration: Release Win32 ------
1>  cpptest.cpp
1>cpptest.cpp(11): error C2118: negative subscript
1>cpptest.cpp(12): error C2118: negative subscript
1>cpptest.cpp(13): error C2118: negative subscript
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========

Implying the sizeof(X) is <130, not <120, and equals to 128.

Monohydroxy answered 5/6, 2020 at 19:43 Comment(0)
B
-5

You can't do this, not with structures. The preprocessor is invoked before compilation takes place, so there isn't even the concept of structure; you can't evaluate the size of something that doesn't exist / wasn't defined. The preprocessor does tokenize a translation unit, but it does so only for the purpose of locating macro invocation.

The closest thing you can have is to rely on some implementation-defined macros that evaluate to the size of built-in types. In gcc, you can find those with:

gcc -dM -E - </dev/null | grep -i size

Which in my system printed:

#define __SIZE_MAX__ 18446744073709551615UL
#define __SIZEOF_INT__ 4
#define __SIZEOF_POINTER__ 8
#define __SIZEOF_LONG__ 8
#define __SIZEOF_LONG_DOUBLE__ 16
#define __SIZEOF_SIZE_T__ 8
#define __SIZEOF_WINT_T__ 4
#define __SIZE_TYPE__ long unsigned int
#define __SIZEOF_PTRDIFF_T__ 8
#define __SIZEOF_FLOAT__ 4
#define __SIZEOF_SHORT__ 2
#define __SIZEOF_INT128__ 16
#define __SIZEOF_WCHAR_T__ 4
#define __SIZEOF_DOUBLE__ 8
#define __SIZEOF_LONG_LONG__ 8

There is really nothing you can do to know the size of a custom struct without writing a program and executing it.

Boudoir answered 7/1, 2014 at 19:53 Comment(3)
I already have a static assert macro which successfully triggers a compile time error based on a sizeof(MyStruct) call so it is false that a program must be executed to know the size of a custom struct. The only thing I am missing is a compiler (as you point out, not a precompiler) command to print the value.Seneschal
I didn't say a program must be executed to know the size of a custom struct - of course the compiler knows it at some point. What I said is that you have no way of asking the compiler to dump it during compilation, so your only choice is to execute a program that does that. Although you can compare it to hard-coded values, there is no instruction you can give it to print the size.Escutcheon
"There is really nothing you can do to know the size of a custom struct without writing a program and executing it."??? how did you know for sure? See my answerMonohydroxy

© 2022 - 2024 — McMap. All rights reserved.