【问题标题】:How can I reliably create a symbol that marks the end of an object?如何可靠地创建标记对象结束的符号?
【发布时间】:2018-08-02 19:42:58
【问题描述】:

我正在做一个项目,我需要创建许多全局变量的地址范围用 C(C++ 不可能),使用 clang。对于完整类型的符号,这很容易以符合标准的方式:

typedef struct range {
    void* begin;
    void* end;
} range;

extern int foo;
range foo_range = { &(&foo)[0], &(&foo)[1] };

但正如我所说,它之所以有效,是因为 C 编译器静态知道 foo 的大小,因此它能够将 &(&foo)[1] 解析为 foo+4 字节(当然,假设 sizeof(int) 为 4)。这不适用于不完整类型的符号:

struct incomplete;
struct trailing_array {
    int count;
    int elements[];
};

extern int foo[];
extern struct incomplete bar;
extern struct trailing_array baz;

range foo_range = { &(&foo)[0], &(&foo)[1] };
// error: foo has incomplete type

range bar_range = { &(&bar)[0], &(&bar)[1] };
// error: bar has incomplete type

range bar_range = { &(&baz)[0], &(&baz)[1] };
// this one compiles, but the range excludes the elements array

不过,多描述这些符号对我来说不是问题。例如,我可以轻松添加元数据:

// foo.h
extern int foo[];
extern size_t foo_size;

// foo.c
int foo[] = {1,2,3};
size_t foo_size = sizeof(foo);

除非这对foo.c 之外的引用没有帮助,因为foo_size 不是编译时常量,因此这不起作用:

range foo_range = { &foo, (void*)&foo + foo_size };
// error: foo_size not a compile-time constant

然而, 起作用的是获取一个符号的地址,该符号的地址正好在我的对象结束的地方。例如,如果我用这个汇编代码定义foo

_foo:
    .long 1
    .long 2
    .long 3
_foo_end:

然后,在我的 C 代码中,我可以:

extern int foo[];
extern int foo_end;
range foo_range = { &foo, &foo_end };

这有效地解决了我的问题。

然而,虽然我可以灵活地添加符号,但我无法灵活地将每个全局声明重写为文件级汇编语句。所以,我的问题是:使用 clang 最接近的方法是什么?

  • 我知道我可以使用节(因为链接器为节创建了开始和结束符号),但是每个全局变量一个节就太过分了。
  • 我知道我不能在我想要获取其范围的全局变量之后立即获取变量的地址,因为已知编译器在某些情况下会重新排序全局变量。李>

我专门使用 Apple 的链接器,但如果您有适用于 GNU ld/gold 或 lld 的解决方案,我仍然会采用它,看看我是否也可以让它在这里工作。

【问题讨论】:

  • int elements[] 不是扩展,是flexible array member(在 C99 中引入)
  • @KeineLust 感谢您提供信息!
  • 我认为有必要提供更多有关您正在尝试做的事情的信息。
  • 除了一些链接器魔术(如您所说的部分)之外,我看不到任何解决方案。否则就不需要“不完整类型”的概念。
  • (void*)&foo + foo_size 是一个问题,因为它试图在 void * 上进行指针数学运算。 (char*)&foo + foo_size 会更有意义。

标签: c linker clang


【解决方案1】:

嗯,如果您在另一个翻译单元中定义它,则没有真正的方法可以做到这一点。如果需要,您可以包含一个带有内容的types.h 文件

struct incomplete {
    char data[SIZE];
}

其中 SIZE 是您喜欢的任何整数,并对每个全局变量执行相同的操作。然而,这将与未来的定义相交。真的,你不得不去

#define INCOMPLETE_SIZE 5

然后将其用于range = { &bar, (void*)&bar + INCOMPLETE_SIZE }

“不完整类型”只是一些标准术语,用于正确描述如何解析

struct A {
     A* ptr;
}

据我所知,它们并没有真正被使用。

我也不推荐 &(&foo)[0], &(&foo)[1] 作为获取一系列指针的方法,它非常深奥/难以阅读。更优选的是&foo, &foo + 1。您可以看到如何通过执行&bar, (void*)&bar + SIZE 将其变成bar 的解决方案,其中SIZE 是您必须在代码中的某处指定的常量(通过声明它并使用sizeof / &foo+1 解决方案,或使用#define 定义SIZE

【讨论】:

  • 我不需要将该文件与完整类型链接,因为这些是完全有效的前向声明,可以在另一个翻译单元中定义。
  • 如果您在另一个翻译单元中有定义,编译器就无法知道大小。您可以使用常量,如我的#define 示例所示。真的没有任何其他解决方案。
  • { &foo, (void*)&foo + foo_size } 的问题是因为您使用了size_t foo_size,而foo_size 不是常量。只需执行{ &foo, (void*)&foo + sizeof(foo)}{ &foo, &foo + 1 } 即可。
  • 不,它没有。 Sizeof 仅适用于完整类型,它从不适用于带有尾随数组的结构。问题中都说明了这一点。
  • 好吧,我假设foo 是来自size_t foo_size = sizeof(foo); 的完整类型,否则错误会在那里发生。 (foo这个词似乎有多种用法,有点混乱)
猜你喜欢
  • 2011-07-30
  • 1970-01-01
  • 2015-07-05
  • 1970-01-01
  • 2014-10-16
  • 1970-01-01
  • 2018-09-26
  • 2014-01-10
相关资源
最近更新 更多