简体   繁体   中英

How can I print the offset of a struct member at compile time?

Given a struct, for instance:

struct A {
    char a;
    char b;
} __attribute__((packed));

I want the offset of b (in this example, 1) in the struct to be printed at compile time - I don't want to have to run the program and call something like printf("%zu", offsetof(struct A, b)); because printing is non-trivial on my platform. I want the offset to be printed by the compiler itself, something like:

> gcc main.c
The offset of b is 1

I've tried a few approaches using #pragma message and offsetof , with my closest being:

#define OFFSET offsetof(struct A, b)
#define XSTR(x) STR(x)
#define STR(x) #x

#pragma message "Offset: " XSTR(OFFSET)

which just prints:

> gcc main.c
main.c:12:9: note: #pragma message: Offset: __builtin_offsetof (struct A, b)

which does not print the numeric offset. It's possible to binary-search the offset at compile time by using _Static_assert - but my real structs are big and this can get a bit cumbersome.

I suspect the stated constraint “I want the offset to be printed by the compiler itself” is an XY problem and that we merely need the offset to be printed by the build tools on the system used for building, not specifically by the compiler.

#if GenerateStructureOffsets
    __asm__("# offsetof(struct A, b) = %c0" : : "i" (offsetof(struct A, b)));
#endif

Given this macro:

#define PRINT_OFFSETOF(A, B) char (*__daniel_kleinstein_is_cool)[sizeof(char[offsetof(A, B)])] = 1

Surprisingly, looks like __builtin_choose_expr works inside __deprecated__ function attribute. The following program:

#include <stddef.h>

struct A {
    char a;
    char b;
} __attribute__((packed));

#define printval_case(x, xstr, y, ...)  __builtin_choose_expr(x == y, xstr"="#y, __VA_ARGS__)
#define printval(x) do { \
    __attribute__((__deprecated__( \
        printval_case(x, #x, 0, \
        printval_case(x, #x, 1, \
        printval_case(x, #x, 2, \
        printval_case(x, #x, 3, \
        /* etc... */ \
        (void)0 )))) \
    ))) void printval() {} \
    printval(); \
} while (0)

int main() {
    printval(offsetof(struct A, a));
    printval(offsetof(struct A, b));
}

When compiled, then gcc will output:

<source>:23:30: warning: 'printval' is deprecated: offsetof(struct A, a)=0 [-Wdeprecated-declarations]
<source>:24:30: warning: 'printval' is deprecated: offsetof(struct A, b)=1 [-Wdeprecated-declarations]

In a similar fashion you could embed the value into the executable, (similarly to how CMake detects compiler stuff ):

#include <stddef.h>
struct A {
    char a;
    char b;
} __attribute__((packed));
#define printval_case(x, xstr, y, ...)  __builtin_choose_expr(x == y, xstr"="#y, __VA_ARGS__)
#define embedval(x) do { \
    static const __attribute__((__used__)) const char unused[] = \
        printval_case(x, #x, 0, \
        printval_case(x, #x, 1, \
        printval_case(x, #x, 2, \
        printval_case(x, #x, 3, \
        /* etc... */ \
        (void)0 )))); \
} while (0)
int main() {
    embedval(offsetof(struct A, a));
    embedval(offsetof(struct A, b));
}

then:

$ gcc file.c && strings ./a.out | grep offsetof
offsetof(struct A, b)=1
offsetof(struct A, a)=0

Perhaps a new pre-processing step would be acceptable. This could then be done as a separate step that won't affect your production binary.

offsetdumper.sh

#!/bin/bash
#
# pre-process some source file(s), add a macro + main() and a file with rules
# describing the interesting symbos. Compile and run the result.

dumprulefile="$1"
shift

# Define your own macros, like OFFSET, in the "Here Document" below:
{
gcc -E "$@" && cat<<EOF
#define OFFSET(x,y) do { printf("%s::%s %zu\n", #x, #y, offsetof(x,y)); } while(0)
#include <stddef.h>
#include <stdio.h>
int main() {
EOF
cat "$dumprulefile"
echo '}'
} | g++ -x c - && ./a.out

rules

OFFSET(A,a);
OFFSET(A,b);

source.h

typedef struct {
    char a;
    char b;
} __attribute__((packed)) A;

Example:

$ ./offsetdumper.sh rules *.h
A::a 0
A::b 1

This is a bit fragile and won't work if your source.h includes a main function, so it may need some tinkering to fulfill your needs.

One possible way is to make the offset the size of an array and then pass the address of that array to a function expecting an incompatible pointer so it prints the type:

static int a[offsetof(struct A, b)];
static void foo1(int *p) { (void)p; }
static void foo2(void) { foo1(&a); }

This prints:

x1.c: In function ‘foo2’:
x1.c:13:1: warning: passing argument 1 of ‘foo1’ from incompatible pointer type [enabled by default]
 static void foo2(void) { foo1(&a); }
 ^
x1.c:12:13: note: expected ‘int *’ but argument is of type ‘int (*)[1]’
 static void foo1(int *p) { (void)p; }
             ^

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM