【发布时间】:2011-09-20 19:21:09
【问题描述】:
我将从终极问题开始:在带有 gcc 的 C 中,是否可以将 __func__(或等效地,__FUNCTION__)的值存储在 .rodata(或-mrodata= 点)或其子部分?
完整解释:
假设我有一个日志记录宏:
#define LOG(fmt, ...) log_internal(__FILE__, __LINE__, __func__, fmt, ##__VA_ARGS__)
(当且仅当__VA_ARGS__ 列表为空时,该一元上下文中使用的字符串连接运算符## 使用前面的逗号,从而允许使用带或不带参数的格式字符串。)
然后我就可以正常使用宏了:
void my_function(void) {
LOG("foo!");
LOG("bar: %p", &bar);
}
可能会打印(显然取决于log_internal的实现):
foo.c:201(my_function) foo!
foo.c:202(my_function) bar: 0x12345678
在这种情况下,格式字符串("foo" 和 "bar: %p")和预处理器字符串("foo.c" 和 "my_function")是匿名只读数据,它们会自动放入 .rodata 部分。
但是假设我希望他们去一个不同的地方(我在一个嵌入式平台上运行几乎所有来自 RAM 的速度,但内存限制正在推动将一些东西移动到 ROM 中)。移动__FILE__ 和格式字符串很“容易”:
#define ROM_STR(str) (__extension__({static const __attribute__((__section__(".rom_data"))) char __c[] = (str); (const char *)&__c;}))
#define LOG(fmt, ...) log_internal(ROM_STR(__FILE__), __LINE__, __func__, ROM_STR(fmt), ##__VA_ARGS__)
您不能将__attribute__ 放在匿名字符串上,因此ROM_STR 宏给它一个临时名称,将其附加到特定部分,然后计算起始地址,因此它可以干净地替换。如果您尝试将 char * 变量作为格式字符串传递给 LOG,这将不起作用,但我愿意排除该用例。
通常,编译器会将恰好相同的匿名字符串组合到一个存储位置,因此一个文件中的每个__FILE__ 实例都将共享相同的运行时地址。通过ROM_STR 中的显式命名,每个实例都将获得自己的存储位置,因此在__FILE__ 上使用它实际上可能没有意义。
但是,我想在__func__ 上使用它。问题是__func__ 与__FILE__ 的魔法不同。来自 gcc 手册,“函数名称作为字符串”:
标识符
__func__由翻译器隐式声明,就好像紧跟在每个函数定义的左大括号之后,声明
static const char __func__[] = "function-name";
出现,其中 function-name 是词法封闭函数的名称。该名称是函数的朴素名称。 ... 这些标识符不是预处理器宏。在 GCC 3.3 和更早版本中,以及仅在 C 中,
__FUNCTION__和__PRETTY_FUNCTION__被视为字符串文字;它们可用于初始化 char 数组,并且可以与其他字符串 literas 连接。 GCC 3.4 及更高版本将它们视为变量,例如__func__。
因此,如果你用ROM_STR 包裹__func__,你会得到
error: invalid initializer
如果你尝试在使用__func__之前或之后放置一个section属性,你会得到
error: expected expression before ‘__attribute__’
或
error: expected ‘)’ before ‘__attribute__’
因此我们回到开头的问题:是否可以将__func__ 存储在我选择的部分中?也许我可以使用-fdata-sections 并做一些链接脚本魔术来将.rodata.__func__.* 排除在.rodata 的其余部分之外?如果是这样,在链接描述文件中排除排除的语法是什么?换句话说,在某处你有一个 *(.rodata*) - 我可以在其他地方放置一个 *(.rodata.__func__*),但我需要修改原始 glob 以排除它,这样我就不会得到两个副本。
【问题讨论】: