C/C++语言中如何使用宏打印行号、文件名和函数名

本文介绍了一种在C/C++中使用宏来打印当前文件名、行号及函数名的方法。通过定义一个宏LOG,可以在任何函数内轻松地调用它来输出这些调试信息,这对于开发过程中的调试非常有用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

C/C++语言中如何使用宏打印行号、文件名和函数名


#include<stdio.h>
#define LOG (printf("%s (%d) - <%s>\n",__FILE__,__LINE__,__FUNCTION__),printf)

void test(){
	LOG;
}
int main(){
	test();
	LOG;
	return 0;
}


<think>我们希望在回调函数中添加函数名文件名行号等信息,以便在日志中记录这些信息。 思路: 1. 修改回调函数的存储方式,使其能够存储额外的元信息(如函数名文件名行号)。 2. 在调用回调函数时,除了传递原始参数,还要传递这些元信息(或者将其作为日志的一部分打印出来)。 然而,函数名文件名行号通常是编译期常量,我们可以通过在注册回调时捕获这些信息。 具体实现步骤: 1. 修改CallbackBaseTypedCallback,使其支持存储调用时传递额外的元信息(但注意:我们可能不希望改变原有回调函数的签名,因为原有回调函数并不需要知道这些信息)。 2. 我们可以考虑在回调容器中存储一个包含元信息的结构体,然后在调用回调函数时,将元信息作为日志的一部分打印出来,而不是传递给回调函数本身。 因此,我们可以设计一个包装器,在调用回调函数之前或之后打印日志。 方案A:在回调函数执行前后自动打印日志(不改变回调函数的签名) - 定义一个结构体,保存元信息(函数名文件名行号原始回调函数。 - 在调用原始回调函数之前,先打印日志(包括元信息参数?但参数可能不易打印,且类型各异)。 方案B:允许回调函数接受额外的参数(即元信息),但这样会改变回调函数的签名,需要回调函数支持额外的参数。这可能会破坏现有的回调函数。 考虑到兼容性,我们采用方案A,但注意:我们无法在不知道参数类型的情况下打印参数(因为参数类型是任意的)。所以,我们只打印元信息(函数名文件名行号回调函数的调用(不打印参数),或者让用户提供一个自定义的日志打印函数。 另一种思路:将元信息存储在回调对象中,当调用回调时,我们可以选择在外部记录日志(比如在容器notifyAll时记录),但这样容器就需要知道这些元信息。 具体实现: 1. 定义一个元信息结构体: struct CallbackMeta { const char* function_name; const char* file_name; int line_number; }; 2. 修改TypedCallback,使其存储元信息,并在调用时使用这些元信息打印日志(但是,这样会强制每个回调都打印日志,不够灵活)。因此,我们可以将日志打印行为提取出来,作为可选的。 3. 我们可以在CallbackContainer中存储一个pair:回调函数+元信息。然后在notifyAll时,先打印元信息,再调用回调。 但是,这样打印的日志无法包含参数信息(因为参数是类型擦除的,我们不知道如何打印)。 因此,我们可能需要用户提供一个日志函数,该函数接受元信息参数,并负责打印。但是,由于参数类型是未知的,这很难实现。 折中方案:只记录元信息(不记录参数),并在调用回调前后打印。 我们修改CallbackBase,增加一个用于打印日志的虚函数(在调用invoke之前调用),这样我们就可以在基类中存储元信息。 重新设计CallbackBaseTypedCallback: ```cpp #include <iostream> #include <functional> #include <vector> #include <memory> #include <tuple> #include <utility> // 元信息结构体 struct CallbackMeta { const char* function_name; // 注册的回调函数名(或调用者指定的名称) const char* file_name; // 文件名 int line_number; // 行号 }; // 回调基类 class CallbackBase { public: virtual ~CallbackBase() = default; virtual void invoke() = 0; // 新增:获取元信息 virtual const CallbackMeta& meta() const = 0; }; // 具体回调模板 template <typename... Args> class TypedCallback : public CallbackBase { public: using FuncType = std::function<void(Args...)>; // 构造函数增加元信息参数 TypedCallback(FuncType func, CallbackMeta meta, Args&&... args) : m_func(std::move(func)), m_meta(meta), m_args(std::make_tuple(std::forward<Args>(args)...)) {} void invoke() override { callImpl(std::index_sequence_for<Args...>{}); } const CallbackMeta& meta() const override { return m_meta; } private: template <size_t... Is> void callImpl(std::index_sequence<Is...>) { if (m_func) { m_func(std::get<Is>(m_args)...); } } FuncType m_func; CallbackMeta m_meta; std::tuple<typename std::decay<Args>::type...> m_args; }; class CallbackContainer { public: ~CallbackContainer() = default; // 添加回调函数,并传入元信息 template <typename Func, typename... Args> void addCallback(Func&& func, CallbackMeta meta, Args&&... args) { using CallbackType = TypedCallback<Args...>; m_callbacks.emplace_back( std::make_unique<CallbackType>( std::forward<Func>(func), meta, std::forward<Args>(args)... ) ); } // 对于成员函数,同样需要添加元信息 template <typename T, typename R, typename... Args, typename... ActualArgs> void addCallback(T* obj, R (T::*mem_func)(Args...), CallbackMeta meta, ActualArgs&&... args) { auto bind_func = std::bind(mem_func, obj, std::forward<ActualArgs>(args)...); addCallback(std::move(bind_func), meta); } template <typename T, typename R, typename... Args, typename... ActualArgs> void addCallback(const T* obj, R (T::*mem_func)(Args...) const, CallbackMeta meta, ActualArgs&&... args) { auto bind_func = std::bind(mem_func, obj, std::forward<ActualArgs>(args)...); addCallback(std::move(bind_func), meta); } // 通知所有回调,并可以选择打印日志 void notifyAll(bool log = false) { for (auto& cb : m_callbacks) { if (cb) { if (log) { // 打印元信息 const CallbackMeta& meta = cb->meta(); std::cout << "Callback [" << meta.function_name << "] at " << meta.file_name << ":" << meta.line_number << " is called." << std::endl; } cb->invoke(); } } } void clear() { m_callbacks.clear(); } private: std::vector<std::unique_ptr<CallbackBase>> m_callbacks; }; ``` 使用示例: 我们需要在添加回调时传入元信息。为了方便地获取函数名文件名行号,我们可以使用。 注意:函数名(__FUNCTION__ 或 __func__)是编译期信息,但不同编译器可能不同。C++11标准中有__func__,但它是函数局部变量(字符串),不能直接用于模板参数。我们可以使用来捕获。 定义一个来简化注册: ```cpp #define ADD_CALLBACK(container, func, ...) \ container.addCallback(func, CallbackMeta{__func__, __FILE__, __LINE__}, ##__VA_ARGS__) // 对于成员函数,我们可以定义另一个 #define ADD_MEMBER_CALLBACK(container, obj, mem_func, ...) \ container.addCallback(obj, mem_func, CallbackMeta{__func__, __FILE__, __LINE__}, ##__VA_ARGS__) ``` 注意:上面的中,我们使用`__func__`来获取当前函数的名称,但注意这个名称是所在位置的函数名(即调用ADD_CALLBACK的函数名),而不是回调函数的名字。如果我们希望记录的是回调函数本身的名字,那么就需要在注册时显式写出。 因此,我们可以修改,让用户指定一个函数名字符串: ```cpp #define ADD_CALLBACK(container, func, ...) \ container.addCallback(func, CallbackMeta{#func, __FILE__, __LINE__}, ##__VA_ARGS__) // 但是#func只能得到func的字符串形式(如果func是函数指针,可能得到的是类型,而不是名字),所以这并不可靠。 // 另一种方法:要求用户传递一个字符串表示的函数名 #define ADD_CALLBACK_WITH_NAME(container, func, func_name, ...) \ container.addCallback(func, CallbackMeta{func_name, __FILE__, __LINE__}, ##__VA_ARGS__) ``` 由于C++没有标准的反射机制,获取函数名字符串比较困难。我们可以让用户显式提供。 使用示例: ```cpp void my_function(int x) { std::cout << "my_function: " << x << std::endl; } class MyClass { public: void method(int x) { std::cout << "MyClass::method: " << x << std::endl; } }; int main() { CallbackContainer container; MyClass obj; // 添加普通函数,并手动指定函数名 ADD_CALLBACK_WITH_NAME(container, my_function, "my_function", 42); // 添加成员函数 ADD_CALLBACK_WITH_NAME(container, &obj, &MyClass::method, "MyClass::method", 43); // 或者,我们可以使用来简化,但需要用户注意函数名的传递 container.notifyAll(true); return 0; } ``` 但是,上面的ADD_CALLBACK_WITH_NAME在成员函数注册时,需要将对象成员函数指针分开传递,这样写起来比较麻烦。我们可以改进,使其能够统一处理普通函数成员函数。 然而,由于普通函数成员函数的注册方式不同,我们可能需要两个。 另一种思路:我们放弃在中自动获取函数名,而是让用户在使用时自己写一个字符串表示的函数名。因为自动获取函数名(特别是成员函数)在C++中比较困难。 因此,我们修改元信息结构体的构造函数,并定义如下: ```cpp #define ADD_CALLBACK(container, func, name, ...) \ container.addCallback(func, CallbackMeta{name, __FILE__, __LINE__}, ##__VA_ARGS__) #define ADD_MEMBER_CALLBACK(container, obj, mem_func, name, ...) \ container.addCallback(obj, mem_func, CallbackMeta{name, __FILE__, __LINE__}, ##__VA_ARGS__) ``` 这样,用户在使用时需要提供一个字符串作为函数名使用示例: ```cpp int main() { CallbackContainer container; MyClass obj; // 添加普通函数 ADD_CALLBACK(container, my_function, "my_function", 42); // 添加成员函数 ADD_MEMBER_CALLBACK(container, &obj, &MyClass::method, "MyClass::method", 43); container.notifyAll(true); return 0; } ``` 输出: Callback [my_function] at example.cpp:100 is called. my_function: 42 Callback [MyClass::method] at example.cpp:101 is called. MyClass::method: 43 注意:行号(__LINE__)是展开处的行号,即调用ADD_CALLBACK的那一行。 问题:在调用回调时,我们打印了元信息,但是参数值没有打印。因为参数类型是任意的,我们无法在不知道类型的情况下打印它们。如果用户需要打印参数,那么需要提供额外的日志函数,这会使设计复杂化。 因此,当前设计只记录打印回调的元信息(函数名文件名行号),不包含参数。 如果用户需要打印参数,可以自己在回调函数内部打印,或者使用一个包装器lambda,例如: ```cpp int value = 42; ADD_CALLBACK(container, [value]() { std::cout << "Callback called with value: " << value << std::endl; // 然后调用实际回调 actual_callback(value); }, "my_callback_wrapper"); ``` 这样,在lambda内部可以打印参数。 总结:我们通过存储元信息并在调用回调前打印,实现了基本的日志功能。用户可以通过方便地注册回调并记录位置信息。 注意事项: 1. 元信息中的函数名字符串由用户提供,因此需要确保其正确性。 2. 文件名行号展开处的位置,即注册回调的位置。 3. 参数打印需要用户自行处理。 最后,关于引用[1]中提到的std::forward,我们在代码中已经使用,用于完美转发参数。 关于引用[2]中的可变参数打印,由于我们使用的是模板参数包,不是C风格的可变参数,所以不适用。 关于引用[3]中的C#代码,与本问题无关。 因此,我们实现了支持日志信息的回调容器。</think># 在C++通用回调函数中添加函数名文件名行号信息 在通用回调函数中添加函数名文件名行号信息是实现详细日志记录的关键技术。我将介绍一种基于编译期信息的实现方案,确保在回调执行时能够自动捕获这些元数据。 ## 实现方案 ### 1. 元信息结构体设计 ```cpp #include <string> #include <functional> // 回调元信息结构体 struct CallbackMeta { std::string function_name; // 函数名 std::string file_name; // 文件名 int line_number; // 行号 }; ``` ### 2. 增强的回调容器实现 ```cpp #include <vector> #include <memory> #include <tuple> // 回调基类 class CallbackBase { public: virtual ~CallbackBase() = default; virtual void invoke() = 0; virtual const CallbackMeta& meta() const = 0; }; // 具体回调模板(支持元信息) template <typename... Args> class TypedCallback : public CallbackBase { public: using FuncType = std::function<void(Args...)>; TypedCallback(FuncType func, CallbackMeta meta, Args&&... args) : m_func(std::move(func)), m_meta(std::move(meta)), m_args(std::make_tuple(std::forward<Args>(args)...)) {} void invoke() override { callImpl(std::index_sequence_for<Args...>{}); } const CallbackMeta& meta() const override { return m_meta; } private: template <size_t... Is> void callImpl(std::index_sequence<Is...>) { if (m_func) { m_func(std::get<Is>(m_args)...); } } FuncType m_func; CallbackMeta m_meta; std::tuple<typename std::decay<Args>::type...> m_args; }; // 增强的回调容器 class EnhancedCallbackContainer { public: ~EnhancedCallbackContainer() = default; // 添加回调(带元信息) template <typename Func, typename... Args> void addCallback(Func&& func, CallbackMeta meta, Args&&... args) { using CallbackType = TypedCallback<Args...>; m_callbacks.emplace_back( std::make_unique<CallbackType>( std::forward<Func>(func), std::move(meta), std::forward<Args>(args)... ) ); } // 通知所有回调(带日志输出) void notifyAll(bool log = true) { for (auto& cb : m_callbacks) { if (cb) { if (log) { const auto& meta = cb->meta(); std::cout << "Invoking callback: " << meta.function_name << " (File: " << meta.file_name << ", Line: " << meta.line_number << ")\n"; } cb->invoke(); } } } void clear() { m_callbacks.clear(); } private: std::vector<std::unique_ptr<CallbackBase>> m_callbacks; }; ``` ### 3. 注册实现 ```cpp // 编译期信息捕获 #define ADD_CALLBACK(container, func, ...) \ container.addCallback(func, \ CallbackMeta{__func__, __FILE__, __LINE__}, \ ##__VA_ARGS__) #define ADD_MEMBER_CALLBACK(container, obj, mem_func, ...) \ do { \ auto bind_func = [obj](auto&&... args) { \ return (obj->*mem_func)(std::forward<decltype(args)>(args)...); \ }; \ container.addCallback(bind_func, \ CallbackMeta{__func__, __FILE__, __LINE__}, \ ##__VA_ARGS__); \ } while(0) ``` ### 4. 使用示例 ```cpp class Processor { public: void process(int value) { std::cout << "Processing value: " << value << std::endl; } void analyze(const std::string& data) const { std::cout << "Analyzing data: " << data << std::endl; } }; int main() { EnhancedCallbackContainer container; Processor processor; // 注册普通函数回调 ADD_CALLBACK(container, [](int x) { std::cout << "Lambda callback: " << x << std::endl; }, 42); // 注册成员函数回调 ADD_MEMBER_CALLBACK(container, &processor, &Processor::process, 100); // 注册const成员函数回调 ADD_MEMBER_CALLBACK(container, &processor, &Processor::analyze, "Important Data"); // 执行所有回调(带日志输出) container.notifyAll(); return 0; } ``` ### 5. 输出示例 ``` Invoking callback: main (File: main.cpp, Line: 35) Lambda callback: 42 Invoking callback: main (File: main.cpp, Line: 38) Processing value: 100 Invoking callback: main (File: main.cpp, Line: 41) Analyzing data: Important Data ``` ## 关键技术点 1. **编译期信息捕获**: - `__func__`:当前函数名C++11标准) - `__FILE__`:当前源文件名 - `__LINE__`:当前行号 - 这些在编译时展开为常量字符串整数[^1] 2. **完美转发**: - 使用`std::forward`保持参数的原始值类别 - 确保高效传递参数,避免不必要的拷贝[^1] 3. **Lambda捕获**: - 使用lambda表达式捕获对象指针成员函数 - 保持类型安全的同时简化成员函数绑定 4. **元信息存储**: - 在回调对象中存储元信息结构体 - 在调用时通过虚函数接口访问 5. **日志集成**: - 在`notifyAll()`中自动输出回调元信息 - 可通过参数控制日志输出开关 ## 数学表示 回调元信息的捕获过程可表示为: $$ \text{meta} = (\text{func}, \text{file}, \text{line}) $$ 其中: - $\text{func}$ 是当前函数名 $f_{\text{current}}$ - $\text{file}$ 是当前文件名 $f_{\text{source}}$ - $\text{line}$ 是当前行号 $l_{\text{current}}$ ## 应用场景 1. **调试追踪**: ```cpp // 在回调执行失败时输出详细信息 try { cb->invoke(); } catch (const std::exception& e) { std::cerr << "Callback failed at " << cb->meta().file_name << ":" << cb->meta().line_number << " - " << e.what() << std::endl; } ``` 2. **性能分析**: ```cpp // 测量回调执行时间 auto start = std::chrono::high_resolution_clock::now(); cb->invoke(); auto end = std::chrono::high_resolution_clock::now(); std::cout << "Callback " << cb->meta().function_name << " took " << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() << " ms" << std::endl; ``` 3. **事件溯源**: ```cpp // 记录回调调用历史 struct CallbackRecord { CallbackMeta meta; std::chrono::system_clock::time_point timestamp; }; std::vector<CallbackRecord> callback_history; void logCallback(const CallbackBase& cb) { callback_history.push_back({ cb.meta(), std::chrono::system_clock::now() }); } ``` ## 注意事项 1. **字符串存储**: - `__FILE__`可能返回完整路径,考虑使用`std::filesystem::path`提取文件名 ```cpp #include <filesystem> std::string file_name = std::filesystem::path(__FILE__).filename().string(); ``` 2. **函数名限制**: - `__func__`返回当前函数名,非回调函数名 - 对于回调函数名,需要显式指定或使用编译器扩展(如`__PRETTY_FUNCTION__`) 3. **性能考量**: - 元信息捕获在编译时完成,无运行时开销 - 字符串操作可能影响性能,在高频回调中慎用 4. **线程安全**: - 在多线程环境中使用需要添加互斥锁保护 - 考虑使用`std::lock_guard`保护回调容器
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值