问题描述
考虑以下代码:
int __attribute__((warn_unused_result)) foo(void)
{
return 42;
}
int main(void)
{
foo();
}
编译后会发出警告:
$ gcc main.c
main.c: In function ‘main’:
main.c:8:5: warning: ignoring return value of ‘foo’,declared with attribute warn_unused_result [-Wunused-result]
8 | foo();
| ^~~~~
这是预期的。我想知道的是,为什么没有使用此属性声明许多标准库函数的任何理由?我说的是像scanf
这样的函数,其中在大多数情况下检查返回值至关重要,而像malloc
这样的函数,如果不使用返回值,则完全没有意义。但是,realloc
似乎有这个。
是否有任何理由不使用__attribute__((warn_unused_result))
声明scanf,malloc等函数?我认为这可以避免许多错误。
解决方法
思考为什么这些特定的函数为何获得warn_unused_result
可能很有启发性-这可以帮助我们弄清楚为什么其他函数没有获得该属性。在我的Glibc系统上,只有两个这样的功能:
extern void *realloc (void *__ptr,size_t __size)
__THROW __attribute_warn_unused_result__ __attribute_alloc_size__ ((2));
extern void *reallocarray (void *__ptr,size_t __nmemb,size_t __size)
__THROW __attribute_warn_unused_result__
__attribute_alloc_size__ ((2,3));
在我的macOS系统上,有三个:
void *malloc(size_t __size) __result_use_check __alloc_size(1);
void *calloc(size_t __count,size_t __size) __result_use_check __alloc_size(1,2);
void *realloc(void *__ptr,size_t __size) __result_use_check __alloc_size(2);
那么,为什么这个超级有用的属性只在realloc()
和Glibc中的另一个几乎相同的函数上使用?
答案是因为此属性旨在防止出现非常具体的错误。假设我们有一个100个元素的数组,想要调整它的大小以添加第101个元素(索引为100)(让我们忽略错误处理):
// Create 100-element array.
int *arr = malloc(sizeof(int) * 100);
arr[99] = 1234;
// Resize and add a 101st element.
realloc(arr,sizeof(int) * 101); // bug
arr[100] = 1234;
发现错误?这是一个严重的内存错误,但它还是一个内存错误,它可能会很快或可能不会很快被注意到。最初的malloc
通常会首先取整到较大的大小,并且如果realloc
成功,则无论如何您都可以使用相同的地址。仅当您开始写入另一个对象或在旧地址为arr
分配了另一个对象时,才会发现实际的错误。
这可能会花一些时间来调试。
因此,警告旨在捕获难以调试的严重错误。与以下内容进行比较:
malloc(100 * sizeof(int));
此错误根本不是很严重,仅是内存泄漏。甚至都不是错误的代码!
作为一个有趣的练习,请尝试将上面的代码放入Godbolt并查看程序集的输出-GCC实际上将完全删除对malloc
的调用。
关于printf
,scanf
和其他人—
int r = printf(...);
if (r == -1) {
abort();
}
如果GCC发出不检查printf
返回的警告,人们会感到沮丧,因为他们会在代码中看到太多警告,并且会通过关闭警告来做出响应。
因此有时最好只在最严重的情况下触发警告,而让其他一些情况滑动。
作为次要技术说明,这对于像Glibc团队这样的标准库维护者,而不是对GCC或Clang而言,更多地是一个问题。即使标准库和编译器紧密集成在一起,它们也是独立的项目,您甚至可以换出不同的标准库。例如,您可以使用musl,glibc或Apple的libSystem。
,- 它不是便携式的。
- 标准不需要要求。
- 如果需要警告,可以随时编写包装。
static inline void * __attribute__((warn_unused_result,always_inline)) mymalloc(size_t size)
{
return malloc(size);
}
int main(void)
{
mymalloc(300);
}