簡體   English   中英

"如何在編譯時打印結構成員的偏移量?"

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

給定一個結構,例如:

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

我希望在編譯時打印結構中b的偏移量(在本例中為 1) - 我不想運行程序並調用類似printf("%zu", offsetof(struct A, b)); 因為在我的平台上打印並非易事。 我希望編譯器本身打印偏移量,例如:

> gcc main.c
The offset of b is 1

我嘗試了一些使用#pragma messageoffsetof的方法,我最接近的是:

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

#pragma message "Offset: " XSTR(OFFSET)

只打印:

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

它不打印數字偏移量。 可以使用_Static_assert在編譯時對偏移量進行二進制搜索 - 但我的真實結構很大,這可能會有點麻煩。

我懷疑聲明的約束“我希望編譯器本身打印偏移量”是一個 XY 問題,我們只需要由用於構建的系統上的構建工具打印偏移量,而不是專門由編譯器打印。

在這種情況下,GCC 和 Clang 能夠在其匯編輸出中包含任意文本,<\/a>並在該文本中包含各種數據操作數,包括結構偏移的立即值。

在任何函數中,包括以下幾行:

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

給定這個宏:

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

令人驚訝的是,看起來__builtin_choose_expr__deprecated__函數屬性中工作。 以下程序:

#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));
}

編譯后,gcc 會輸出:

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

以類似的方式,您可以將值嵌入到可執行文件中(類似於 CMake 檢測編譯器內容的方式):

#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));
}

然后:

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

也許新的預處理步驟是可以接受的。 這可以作為一個單獨的步驟完成,不會影響您的生產二進制文件。

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;

例子:

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

這有點脆弱,如果你的source.h包含一個main函數,它就不起作用,所以它可能需要一些修補來滿足你的需求。

一種可能的方法是將偏移量設置為數組的大小,然后將該數組的地址傳遞給期望不兼容指針的函數,以便打印類型:

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

這打印:

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; }
             ^

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM