构造一个指向 alloca 的函数指针会导致链接器错误?

Posted

技术标签:

【中文标题】构造一个指向 alloca 的函数指针会导致链接器错误?【英文标题】:Constructing a function pointer to alloca causes linker errors? 【发布时间】: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 中工作,但函数的地址不会。 【参考方案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的手册页:

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

该页面还提到:

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

【讨论】:

以上是关于构造一个指向 alloca 的函数指针会导致链接器错误?的主要内容,如果未能解决你的问题,请参考以下文章

C++ 使用指向相同函数的指针作为模板参数是不是总是会导致相同的实例化?

在构造函数初始化器列表中初始化函数指针数组 - 错误

将对象的指针提供给构造函数会导致对象被破坏[重复]

C++函数指针与成员函数指针

类中的无限构造

深浅拷贝