编辑:它有效!
我知道这是不可能的,因为我已经花了几个小时在上面,并且还阅读了下面的内容,但我决心近似 clang 块前向定义,即使它需要链接器技巧或内联汇编:
我正在使用为 gcc 编写的宏库(在输出大量 json 时比 jansson 快 1,000 - 10,000 倍),这鼓励了像这个测试用例这样的恐怖:
JSON_with_BUFFERED_WRITER((1001, result),
JSON_OBJ(
JSON_STRING("logGroupName", "\005group©"),
JSON_STRING("logStreamName", "stream")
)
) (out) {
strncpy(out, data, length); // warning not checking target bounds
out+=length;
*out=0;
return 0;
};
是的,最后的那个块实际上部分地被吸进宏扩展中,以产生如下所示的函数定义:
int __WRITER__1849_emit(const char *data, int length, typeof(result) out) {
strncpy(out, data, length);
out += length;
*out = 0;
return 0;
};
宏展开后出现的(
(out)
实际上变成了宏名称的参数,宏名称是宏展开的结果,并且它消耗了out
,然后将其作为生成的参数列表的最后一项发出,这是多么恶心那?我为此感到非常自豪!)
在 gcc 下,此函数(通过宏扩展中的早期前向定义
auto int __WRITER__1849_emit(const char *data, int length, typeof(result) out);
)被分配给宏扩展中某处的结构体实例,并在执行宏生成的代码时作为回调调用。
大部分库已通过 clang 块转换为在 clang 下工作,通常以最终函数为前缀,将该块分配给准备使用的结构字段。
__WRITER__1849.emit = ^int(const char *data, int length, typeof(result) out) {...
在分配之前没有使用的情况下效果很好,但在
JSON_with_BUFFERED_WRITER
的情况下,处理是在分配发生之前在宏参数扩展内部发生的。
可见前向声明的重要性。无论如何,这些值在链接时都是已知的,但我需要在执行宏生成的代码之前将值插入到结构实例静态初始化中。
我知道可以改进用法并进行两阶段声明然后使用,但并不是那种让我醒来想要编写更多代码的退出(可能是永远不应该的类型)写的,但这不是 Reddit,所以不要评判我)。
虽然最终函数可能会被吸入宏扩展的尾部,甚至可以通过宏尾部扩展为左值来分配给某些东西,但我无法选择在分配后调用任何内容。 C 没有设置器,我无法弄清楚如何让任何东西超出范围来在赋值后运行析构函数(在其中我可以做某事),所以这是一个死胡同。
我尝试通过
alias
属性尝试说服 clang:
static __attribute__((alias("__WRITER__1849_emit__"))) int (^__WRITER__1849_emit)(const char *data, int length, typeof(result) cookie);
...
__attribute__((external)) static int (^__WRITER__1849_emit__)(const char *data, int length, typeof(result) cookie) =
^int(const char *data, int length, typeof(result) out) {
strncpy(out, data, length);
out += length;
*out = 0;
return 0;
};
但是它不起作用,并且检查.s文件,别名属性没有效果。
不可能将相反的
__attribute__((alias("__WRITER__1849_emit"))
添加到静态块定义中,因为它是“前向”声明,也许因为它是静态定义的。
extern
,但我还没有弄清楚将后续静态定义链接并与外部定义匹配所需的技巧:
extern __attribute__((alias("__WRITER__1849_emit__"))) int (^__WRITER__1849_emit)(const char *data, int length, typeof(result) cookie);
...
static int (^__WRITER__1849_emit__)(const char *data, int length, typeof(result) cookie) =
^int(const char *data, int length, typeof(result) out) {
strncpy(out, data, length);
out += length;
*out = 0;
return 0;
};
这仅仅给出了
undefined reference to '__WRITER__1849_emit'
,而.s文件中没有任何内容表明符号之间将发生任何别名或链接,并且extern
“前向声明”黑客的.s文件中的唯一定义是:
.addrsig_sym __WRITER__1849_emit
所以现在我正在寻找一种从函数中的静态块定义和/或可能是一些内联程序集声明/发出全局符号的方法,它可以映射到外部“前向声明”
有什么建议吗?特殊的链接器脚本?
看起来我需要以某种方式发出这样的东西,以使用
extern
方法:
.globl __WRITER__1849_emit
.set __WRITER__1849_emit, __WRITER__1849_emit__
也许是这样的:
__asm__(" .globl __WRITER__1849_emit\n"
".set __WRITER__1849_emit, __WRITER__1849_emit__\n");
好吧,它几乎可以工作了;一个小警告:
warning: relocation against '__WRITER__1849_emit__' in read-only section '.text'
但真正的问题是它是在定义 __WRITER__1849_emit__\
之前发出的(静态常量初始值设定项),从而导致: undefined reference to '__WRITER__1849_emit__'
在链接期间,针对使用 __WRITER__1849_emit
表面上的 extern
进行记录
“前向声明”
我需要像 clang 那样限定我的符号,这与 extern 结合使用!
__asm__(" .globl __WRITER__1849_emit\n"
".set __WRITER__1849_emit, selftest_json.__WRITER__1849_emit__\n");
所以我只需要标准化前缀
selftest_json.
的生成,这可能很难,因为这是外部函数名称,在宏观时间不能作为字符序列使用,但它变得充满希望!
我宁愿让它工作,然后因为它不干净而不使用该解决方案,也不愿让它工作。我会屈服于我的意志!
我使用的是 Ubuntu clang 版本 15.0.7
编辑:也许我可以替换一个全局函数指针,该指针接收符号名称作为参数,与 dlopen 一起使用来查找真实地址