我将从最终的问题开始:在C中使用gcc,是否可以获得__func__
(或等效地,__FUNCTION__
)的值,而不是.rodata
{1}}(或-mrodata=
点)或其中的小节?
完整的解释:
说我有一个记录宏:
#define LOG(fmt, ...) log_internal(__FILE__, __LINE__, __func__, fmt, ##__VA_ARGS__)
(当且仅当##
列表为空时,该一元上下文中使用的字符串连接运算符__VA_ARGS__
使用前面的逗号,从而允许使用带或不带参数的格式字符串。)< / p>
然后我可以正常使用宏:
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数组,它们可以与其他字符串文字连接。 GCC 3.4及更高版本将它们视为变量,例如__func__
。
因此,如果您使用__func__
包裹ROM_STR
,则会获得
error: invalid initializer
如果您尝试在使用__func__
之前或之后设置部分属性,则会得到
error: expected expression before ‘__attribute__’
或
error: expected ‘)’ before ‘__attribute__’
因此我们回到开头问题:是否可以将__func__
存储在我选择的部分中?也许我可以使用-fdata-sections
并使用一些链接器脚本魔法将.rodata.__func__.*
排除在.rodata
的其余部分之外?如果是这样,在链接描述文件中使用排除的globbing的语法是什么?换句话说,在某个地方你有一个*(.rodata*)
- 我可以把*(.rodata.__func__*)
放在其他地方,但我需要修改原始的glob来排除它,所以我不会得到两个副本。
答案 0 :(得分:3)
可能比你想要的更麻烦的一种方法是插入一个脚本来更改编译和汇编之间的节名。例如:
gcc -fdata-sections -S -o test.s test.c
sed 's/^\t.section\t\.rodata\.__func__\.[0-9]*/\t.section .rom_data/' -i test.s
gcc -c test.s
您还可以尝试编写一个clang转换传递,将__func__
声明放在您选择的部分中,或者使用libbfd编写目标文件操作程序。
答案 1 :(得分:2)
看起来我在-fdata-sections
业务的最后回答了我自己的问题,我只是不了解GNU Linker足以看到它。只要我先指定*(.rodata.__func__*)
位,我实际上并不需要排除全局。 glob匹配的任何部分都将被标记为已使用,因此*(.rodata*)
的后续glob不会对它们进行双重计数并将它们复制到其他位置。我根本不需要用ROM_STR
标记它们。酷!
重要的是要注意-fdata-sections
确实将每个函数字符串放入其自己的.rodata.__func__.1234
部分(我不确定数字遵循的模式)。我不知道匿名字符串是否也有自己的部分;如果是这样,我可以使用相同的链接器技巧来捕获所有匿名字符串而不是ROM_STR
部分属性宏,但这可能是一个坏主意。 ROM_STR
在LOG
宏中使用,因此保证仅将其应用于日志记录格式字符串。如果我使用链接器技巧强制所有匿名字符串进入ROM,那将包括正常的消息数据,并且我将支付运行时性能损失以从闪存访问它。所以我不知道它是否可能,但它的可行性取决于您的具体系统要求。