You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将__func__放入指定的链接器段?

解决方案

针对在GCC环境下将日志中__func__/__FUNCTION__的字符串存入指定Flash段的需求,有两种可行方案:

方案1:利用GCC扩展__FUNCTION__自动生成段内字符串

GCC中__FUNCTION__是预处理器宏,会直接展开为当前函数名的字符串字面量(区别于标准C的__func__是隐式const char数组),因此可以复用你处理fmt的技巧,在宏中生成存入指定段的静态数组:

#include <stdarg.h>

// 实际的日志实现函数
void LogImpl(char const *function, char const *fmt, ...) {
    va_list args;
    va_start(args, fmt);
    // 替换为你的日志输出逻辑
    vprintf("%s: ", function);
    vprintf(fmt, args);
    va_end(args);
}

// 日志宏,自动处理函数名和格式字符串的段存储
#define Log(fmt, ...)                                                          \
    ({                                                                         \
        static const char _log_func[] __attribute__((section(".mystr"))) = __FUNCTION__; \
        static const char _log_fmt[] __attribute__((section(".mystr"))) = (fmt); \
        LogImpl(_log_func, _log_fmt, ##__VA_ARGS__);                           \
    })

// 使用示例
int main(void) {
    Log("error %d\n", 4);
    Log("info: %s\n", "test");
    return 0;
}

说明

  • 每个函数内的Log调用会生成唯一的_log_func静态数组(同一函数内多次调用只会生成一次),所有这些数组都会被放入.mystr段。
  • 无需修改函数内部代码,宏自动完成函数名的段存储逻辑。
  • 依赖GCC的__FUNCTION__扩展,所有现代GCC版本均支持。

方案2:基于标准__func__的手动声明方式

如果必须遵循C标准使用__func__,可以在每个需要打日志的函数开头声明一个存入指定段的静态字符串变量,再在日志宏中引用:

#include <stdarg.h>

void LogImpl(char const *function, char const *fmt, ...) {
    va_list args;
    va_start(args, fmt);
    vprintf("%s: ", function);
    vprintf(fmt, args);
    va_end(args);
}

// 定义函数名字符串到指定段的宏
#define DEFINE_LOG_FUNC static const char _log_func[] __attribute__((section(".mystr"))) = __func__;
// 日志宏,引用已定义的函数名字符串
#define Log(fmt, ...) LogImpl(_log_func, fmt, ##__VA_ARGS__)

// 使用示例
int main(void) {
    DEFINE_LOG_FUNC; // 必须在函数开头声明
    Log("error %d\n", 4);
    return 0;
}

void another_func(void) {
    DEFINE_LOG_FUNC;
    Log("another function log\n");
}

说明

  • 符合C99标准的__func__用法,无需依赖GCC专属宏。
  • 缺点是每个需要打日志的函数都必须手动添加DEFINE_LOG_FUNC声明,略显繁琐。

关键注意事项

  1. 链接脚本配置:确保你的链接脚本中显式定义了.mystr段,否则链接器可能会将其合并到默认的.rodata段中。示例链接脚本片段:
    .mystr : {
        *(.mystr)
    } > FLASH /* 指定存储到Flash区域 */
    
  2. 验证段存储:编译后可以用objdump或readelf验证字符串是否在目标段:
    objdump -s -j .mystr your_program.elf
    
  3. 避免重复存储:同一函数内的多次Log调用只会生成一份函数名字符串(因为静态变量只初始化一次),不会造成Flash空间浪费。

为什么你的初始尝试不可行

你尝试用宏展开__func__的字符初始化数组的方法无法生效,因为__func__是编译器在函数内隐式生成的运行时变量(而非编译期常量),C标准要求静态数组的初始化器必须是编译期可求值的常量表达式,因此这种方式不符合语法要求。


内容的提问来源于stack exchange,提问作者stephenbennyhat

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 23:13:09