【问题标题】:Why does not gcc and clang warn for unused result of library functions?为什么 gcc 和 clang 不对库函数的未使用结果发出警告?
【发布时间】:2020-08-25 06:41:09
【问题描述】:

考虑这段代码:

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 等函数?我认为这可以防止许多错误。

【问题讨论】:

  • scanf 具有该属性。 Some 还有其他功能。你必须定义_FORTIFY_SOURCE
  • 也许有人认为 stdio.h 无论如何都是一个绝望的案例。大部分都是很危险的,应该是编程史上所有类别中最糟糕的编程库,它给人类造成了最多的错误和物质损失。与无法挽救的格式字符串、可变参数函数、可怕的 API、大约 35 种不同的显式未定义行为案例相比……好吧,忘记检查 scanf 的结果只是 stdio.h 错误海洋中的一滴。该库提供了许多其他方法来破坏缓冲区溢出和越界访问的代码。
  • @KamilCuk 这很奇怪。我没有收到任何警告。
  • @klutt Aaand 你必须启用优化godbolt link
  • @KamilCuk 用 O3 测试了 clang 和 gcc。没有警告。

标签: c gcc clang compiler-warnings


【解决方案1】:

想想为什么这些特定的函数得到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 的调用。

至于printfscanf等——

int r = printf(...);
if (r == -1) {
    abort();
}

如果 GCC 因未检查 printf 的返回而发出警告,人们会感到沮丧,因为他们会在代码中看到太多警告 - 他们会通过关闭警告来做出回应。

因此,有时最好只对最严重的情况触发警告,而让其他一些情况下滑。


作为一个次要的技术说明,这对于标准库维护者(如 Glibc 团队)而言更多是一个问题,而不是 GCC 或 Clang。尽管标准库和编译器紧密集成,但它们是独立的项目——您甚至可以换出不同的标准库。例如,您可能会使用 musl、glibc 或 Apple 的 libSystem。

【讨论】:

  • only get used 但事实并非如此! godbolt link with scanf unused return warning 你必须-O -D_FORTIFY_LEVEL=1。对于其他功能,这是一个可选功能,因此程序员可以选择行为。
  • 说到printf我完全看懂了,但不是scanf。
  • 完全没用if(realloc(arr, sizeof(*arr) * 101)) arr[100] = 10;返回值使用了同样的错误
  • 顺便说一句 sizeof(int) => sizeof(*arr) 更安全
  • @P__J__ 我无法理解你写的内容。 sizeof(int) 这里用于一致性。
【解决方案2】:
  1. 它不是便携式的。
  2. 标准不要求它。
  3. 如果您想收到警告,您可以随时编写包装器。
static inline void * __attribute__((warn_unused_result, always_inline)) mymalloc(size_t size)
{
    return malloc(size);
}

int main(void)
{
    mymalloc(300);
}

【讨论】:

  • 我看不出可移植性是如何发挥作用的。
  • 另外,标准是否要求 realloc 发出此警告?
  • 我相信属性不是很便携
  • @klutt 标准不要求使用返回值,因此没有警告。
  • 是的,但可移植性并不重要。如果我要用另一个编译器编译一个程序,我不会复制标准库。据我所知,添加该属性不会导致任何可移植性问题。
猜你喜欢
  • 2019-06-14
  • 1970-01-01
  • 1970-01-01
  • 2021-12-27
  • 2018-07-18
  • 2023-04-08
  • 1970-01-01
  • 1970-01-01
  • 2013-06-19
相关资源
最近更新 更多