altendky
altendky

Reputation: 4344

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

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.

Upvotes: 100

Views: 54596

Answers (16)

ChrisZZ
ChrisZZ

Reputation: 2141

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

Upvotes: 2

Gabriel Staples
Gabriel Staples

Reputation: 52449

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://stackoverflow.com/a/71899854/4561887
// 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://stackoverflow.com/a/45477830/4561887
  5. Doing arithmetic on __LINE__ in the C preprocessor:
    1. https://stackoverflow.com/a/24551912/4561887
    2. https://stackoverflow.com/a/24941973/4561887
  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

Upvotes: 2

Lundin
Lundin

Reputation: 213338

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"

Upvotes: 1

user5896643
user5896643

Reputation:

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

Upvotes: 131

JavaMan
JavaMan

Reputation: 5034

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.

Upvotes: -1

James Murray
James Murray

Reputation: 11

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

Upvotes: 1

Zakaria Fadli
Zakaria Fadli

Reputation: 21

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).

Upvotes: 2

urish
urish

Reputation: 9033

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

Upvotes: 5

JavaMan
JavaMan

Reputation: 5034

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.

Upvotes: 29

dsptech
dsptech

Reputation: 43

@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] "

Upvotes: 4

Ruslan
Ruslan

Reputation: 19100

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

Upvotes: 20

Konstantin Stupnik
Konstantin Stupnik

Reputation: 468

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.

Upvotes: 11

user10746164
user10746164

Reputation: 11

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

Upvotes: 1

jws
jws

Reputation: 2764

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.

Upvotes: 1

SIGNOISE
SIGNOISE

Reputation: 31

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).

Upvotes: 3

Filipe Gon&#231;alves
Filipe Gon&#231;alves

Reputation: 21213

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.

Upvotes: -5

Related Questions