【问题标题】:Constructing a function pointer to alloca causes linker errors?构造一个指向 alloca 的函数指针会导致链接器错误?
【发布时间】:2019-08-06 05:21:48
【问题描述】:

我正在尝试编写一个函数,该函数传递一个用于分配的函数作为其参数;它应该接受void *(*)(size_t) 类型的任何有效分配器。但是,当我尝试使用 alloca 作为分配器时,我遇到了奇怪的行为 - 构造一个指向 alloca 函数的函数指针可以正常编译,但会导致链接器错误:

#include <stdlib.h>
#include <alloca.h>

int main() {
  void *(*foo)(size_t) = alloca;
}

结果

/tmp/cc8F67yC.o: In function `main':
test15.c:(.text+0x8): undefined reference to `alloca'
collect2: error: ld returned 1 exit status

这是否与 alloca 内联有关?但是当函数不需要地址时,内联不会仅作为优化来完成。事实上,使用 GCC,我什至可以编写自己的版本,并在上述代码中按预期工作:

static inline void *alloca(size_t n) {
  return __builtin_alloca(n);
}

标准版本的行为方式不同是有原因的吗?

【问题讨论】:

  • 可能是因为编译器直接将alloca的所有调用替换为__builtin_alloca?
  • alloca 并不是一个真正的函数。它是一个内置的编译器,由具有无限智慧和睿智的人赋予了类似函数的语法。您自己的版本在上述代码中按预期工作,但可惜它根本不像alloca 那样工作。如果你给它一个__attribute__((always_inline)),函数本身可能在gcc 中工作,但函数的地址不会。

标签: c function-pointers alloca


【解决方案1】:

你不能按照你的建议去做。 alloca 是一个非常特殊的野兽,它只能在函数体内显式调用,而不能在函数调用的参数表达式中调用。

请注意,alloca 没有标准版本。 C 标准和 POSIX 都没有描述这个函数。

您公开的替代方案,将alloca 重新定义为调用__builtin_alloca 的内联函数不起作用:除其他问题外,__builtin_alloca() 返回的指针仅在调用者返回之前有效,无论它是否内联.

linux man page 非常明确:

[...]

描述

alloca() 函数在堆栈中分配 size 个字节的空间 调用者的框架。这个临时空间会自动释放 当调用alloca()的函数返回给它的调用者时。

返回值

alloca() 函数返回一个指向开头的指针 分配的空间。如果分配导致堆栈溢出,则程序 行为未定义。

[...]

符合

POSIX.1 中没有此功能。

有证据表明alloca()功能出现在32V、PWB、 PWB.2、3BSD 和 4BSD。在 4.3BSD 中有一个手册页。 Linux 使用 GNU 版本。

注意事项

alloca() 函数依赖于机器和编译器。为了 某些应用程序,它的使用可以提高效率相比 使用malloc(3) 加上free(3)。在某些情况下,它还可以 简化使用 longjmp(3) 或的应用程序中的内存释放 siglongjmp(3)。否则,不鼓励使用它。

因为alloca()分配的空间是在栈内分配的 框架,如果函数返回是,该空间会自动释放 通过调用longjmp(3) 或siglongjmp(3) 跳过。

alloca() 分配的空间不会自动释放,如果 指向它的指针只是超出了范围。

不要尝试free(3)alloca()分配的空间!

GNU 版本说明

通常,gcc(1) 使用内联代码将调用转换为 alloca()。 当 -ansi、-std=c89、-std=c99 或 给出了-std=c11 选项,并且不包括标题&lt;alloca.h&gt;。 否则,(没有-ansi 或-std=c* 选项)glibc 版本 &lt;stdlib.h&gt; 包括 &lt;alloca.h&gt; 并包含以下行:

      #ifdef  __GNUC__
       #define alloca(size)   __builtin_alloca (size)
       #endif

如果有这个函数的私有版本,后果会很混乱。

代码被内联的事实意味着不可能采取 这个函数的地址,或者通过链接改变它的行为 使用不同的库。

内联代码通常由一条指令组成 堆栈指针,并且不检查堆栈溢出。因此,有 没有NULL 错误返回。

BUG

如果堆栈帧无法扩展,则没有错误指示。 (但是,在分配失败后,程序很可能会收到 SIGSEGV 信号,如果它试图访问未分配的空间。)

在许多系统上,alloca() 不能在参数列表中使用 函数调用,因为alloca()保留的堆栈空间 将出现在函数空间中间的堆栈上 论据。

【讨论】:

    【解决方案2】:

    谁说你的功能

    static inline void *alloca(size_t n) {
        return __builtin_alloca(n);
    }
    

    有效吗? __builtin_alloca 分配的对象在函数结束时满足它的生命周期,所以一旦你返回它,你就已经有了一个悬空指针!

    【讨论】:

    • 不知何故,我忽略了这一点。测试函数运行良好,但这可能只是因为我在alloca 的堆栈帧中有一个指向地址的指针,并且没有调用任何会覆盖该地址的函数。
    【解决方案3】:

    引用here的手册页:

    代码被内联的事实意味着不可能采取 这个函数的地址,或者通过链接改变它的行为 使用不同的库。

    该页面还提到:

    如果有人拥有此功能的私有版本,后果会很混乱

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-08-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-01-04
      • 2018-10-19
      • 1970-01-01
      相关资源
      最近更新 更多