我有一个功能
void *custom_get_value(ObjectPtr)
此函数传统上从未用于返回NULL。它可以返回以下任何值
uint32_t
int32_t
uint64_t
int64_t
uint8_t
由于函数从未用于返回NULL,因此我有很多代码
*(uint32_t*)custom_get_value(ObjectPtr)
OR
*(uint64_t*)custom_get_value(ObjectPtr)
最近我们决定修改
的行为void *custom_get_value(ObjectPtr) in such a way that it can return NULL.So all occourances of the above scenario (de-referencing to specific types without checking the return value) can result in segmentation fault.
我可以使用一些宏来识别代码中返回值
的所有位置void *custom_get_value(ObjectPtr)
未被检查。如果是,我该怎么做?
答案 0 :(得分:2)
您无法轻松编写可以跟踪值返回后发生的值的宏,因为数据流分析有点超出了预处理器的能力。但是,有一种有用的技术可以帮助解决这种情况,即将要检查的函数定义为自引用宏:
#define custom_get_value(...) (0, custom_get_value(__VA_ARGS__))
这个例子本身并没有做任何事情,但它展示了一些有用的东西:当宏名称作为自己的替换列表的一部分出现时,名称的嵌套出现是"涂成蓝色"并且不会第二次扩展。因此,通过将现有函数名称定义为宏,您可以" wrap" (或"建议")函数的所有现有调用 - 可能在未知数量的地方分散在您的代码中 - 除了保留原始调用之外还有额外的操作。
然后你有几个选择,例如:
#define custom_get_value(...) ({ \
_Pragma("message \"custom_get_value called\""); \
custom_get_value(__VA_ARGS__); \
})
...如果你在GCC或Clang上,应该在编译时列出调用custom_get_value
的每个点的文件和行号。 (#pragma message
和语句表达形式都是非标准的GCC扩展 - 但是如果您只是在清理旧呼叫时才使用它,那么这可能并不重要?一旦检查了所有呼叫你可以删除非标准代码。)
或者应用运行时消息:
#define custom_get_value(...) \
custom_wrap(custom_get_value(__VA_ARGS__), __FILE__, __LINE__)
void * custom_wrap(void * val, char * f, int l) {
printf("custom_get_value called in %s on line %d\n", f, l); return val;
}
...每次调用函数时都会让你疯狂地打印一条消息,但至少是标准的。
你甚至可以使用一个函数来包装所有调用,该函数再次使得结果指针保证为非null,尽管这可能不是处理这种情况的最佳方法。
答案 1 :(得分:0)
将新功能命名为custom_get_value_ex
并删除旧功能。然后你的编译器会非常友好地指出旧函数的所有用法,所以你可以查看它们。
通常,当您更改已在使用的函数的语义时,最好还是检查对函数的所有调用。
也许你可以将一些这些调用包装在一个更易于维护的包装器中,例如
inline uint32_t custom_get_uint32(.....)
{
void *value = custom_get_value(.....);
if ( !value )
return 0; // or some other error handling
return *(uint32_t *)value;
}
然后,如果再次更改custom_get_value
,则只需要对此包装器进行操作。
更为简单的是获得您感兴趣的项目的功能:
inline uint32_t get_propeller_setting(.....)
{
void *value = custom_get_value(PROPELLER_SETTING,........)
// ....
}
然后,您可以对每个值何时返回空指针进行特定处理。