定义我自己的 malloc 后分段错误?

Segmentation fault after defining my own malloc?

提问人:runningupthatroad 提问时间:9/13/2022 最后编辑:Rachid K.runningupthatroad 更新时间:9/20/2022 访问量:192

问:

为什么这会导致分段错误?

#include <stddef.h>
       
void *malloc(size_t s) {           
};                                                                                                    
int main() {  
  printf("lol");           
}

我是这样编译的:

gcc -o l lol.c

我的猜测是调用.printf()malloc()

C GCC 分段 - 内置故障 malloc

评论

3赞 DaBler 9/13/2022
是的,printf 调用 malloc
6赞 Ted Lyngmo 9/13/2022
您的 malloc 甚至不返回 a,因此程序具有未定义的行为。void*
5赞 John Bollinger 9/13/2022
标准库函数的名称保留为具有外部链接的标识符。您的程序具有未定义的行为,因为其本身将其中一个 () 定义为具有外部链接的函数的名称。malloc
1赞 n. m. could be an AI 9/13/2022
@JohnBollinger 所有主要实现都明确允许替换和好友。那些没有的人无论如何可能一文不值。malloc
3赞 zwol 9/13/2022
@runningupthatroad在这种情况下,您需要包括标准库分析在内的整个程序来检测编译时的无限递归。我不知道有任何 C 库实现可以(截至本文)安全地进行全程序分析。但是,我可以使用建议的更改在运行时重现无限递归。

答:

4赞 user253751 9/13/2022 #1

标准库中的某些内容正在调用,期望它返回一个可用的内存地址,并向该地址写入某些内容。malloc()

在 Unixy(或至少是 Linuxy)平台上,当您在主程序中定义库函数时,它会覆盖任何其他库中的函数,即使库调用它,即使定义它的同一库调用它。

评论

0赞 the busybee 9/13/2022
大多数系统的大多数链接器(不仅是 unixoid 目标)都显示此行为。我仍然必须找到一个不这样做的人。
0赞 user253751 9/13/2022
@thebusybee Windows 不会跨 DLL 执行此操作
1赞 the busybee 9/13/2022
Windows 不是链接器,DLL 与链接器使用的库是不同的概念。DLL 引用在运行时解析,但我们在这里谈论的是链接时间。
0赞 the busybee 9/13/2022
OP 的程序在 Win10 上崩溃,也是用 MinGW 编译的。;-)
0赞 user253751 9/13/2022
@thebusybee Microsoft 标准库位于 DLL (msvcrXX) 中。MinGW 的行为可能有所不同。
7赞 John Bollinger 9/13/2022 #2

根据 C 语言规范,将您自己的标准库函数定义作为具有外部链接的函数(这是函数的默认值)会产生未定义的行为。这些名称保留用于此类用途(C17 7.1.3)。您已经观察到这种行为的许多可能表现之一。

您至少有四种选择:

  1. 只需使用标准库的实现即可。
  2. 使用不同的名称定义函数。例如。然后,您需要使用该名称来调用它,尽管您可以通过使用宏来伪装它。my_malloc()
  3. 声明您的函数(赋予其内部链接)。然后,它可以与标准库函数同名,但只有在相同的翻译单元(大致为源文件)中定义的函数才能通过该名称调用它。static
  4. 使用特定 C 实现的特定于实现的条款(见下文)。

一些 C 实现为程序提供了特定于实现的规定,以提供至少某些库函数的自己的版本。Glibc 就是其中之一。但是,这些规定受到很大的限制。

  • 首先,您可以预期,实现将要求替换函数提供相同的二进制接口,并正确实现语言指定的行为。(您的函数不执行后者。

  • 其次,如果函数是一组相关函数的一部分,您可能会发现实现需要您替换整个集合。事实上,Glibc 文档说“替换 malloc”涉及为所有这些函数提供替换:、、、。您的程序也不会这样做。Glibc 文档还建议为其他几个函数提供替代品,并建议如果不这样做,虽然本身不会损害任何 Glibc 函数,但可能会破坏一些程序:、、*、、、、。但是,后者与您的特定示例无关。mallocmallocfreecallocreallocaligned_alloccfreemalloc_usable_sizememalignposix_memalignpvallocvalloc


*只有非常古老的程序才需要。

评论

0赞 n. m. could be an AI 9/13/2022
这是错误的。“未定义的行为”与“不允许你”不同。未定义的行为仅意味着该标准不提供任何保证。如果其他东西(例如特定的实现)提供了所需的保证,这很好。顺便说一句,这里就是这种情况。
0赞 John Bollinger 9/13/2022
@n.1.8e9-where's-my-sharem.,我改进了我的措辞,使其更精确,并另外解决了替换库函数的实现特定规定的问题。
0赞 n. m. could be an AI 9/14/2022
替换 malloc 和 friends 是一种非常普遍的现有做法,它早就应该编入标准中。
1赞 EserRose 9/14/2022
您还可以使用包装器。将函数定义为并使用 --wrap=malloc 标志void *__wrap_malloc(size_t size)
0赞 Rachid K. 9/17/2022 #3

要验证您的函数是否覆盖了 C 库中的同级函数并被 调用,您可以通过调用 system call 在终端(文件描述符编号 1)上记录跟踪:malloc()printf()write()

#include <stdio.h>
#include <unistd.h>  

void *malloc(size_t s) {
  write(1, "I am called\n", sizeof("I am called\n") - 1);           
};                                                                                                    
int main() {  
  printf("lol");           
}

编译后,程序显示:

$ gcc -g overm.c -o overm
$ ./overm
I am called
Segmentation fault (core dumped)

使用调试器对生成的核心转储文件进行分析,如下所示,显示了崩溃时调用堆栈的状态:gdb

$ gdb overm core
[...]
Reading symbols from overm...
[New LWP 8494]
Core was generated by `./overm'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x00007fd4bf7d6dc8 in _IO_new_file_overflow (f=0x7fd4bf9336a0 <_IO_2_1_stdout_>, ch=108) at fileops.c:781
781 fileops.c: No such file or directory.
(gdb) where
#0  0x00007fd4bf7d6dc8 in _IO_new_file_overflow (f=0x7fd4bf9336a0 <_IO_2_1_stdout_>, ch=108) at fileops.c:781
#1  0x00007fd4bf7d8024 in __GI__IO_default_xsputn (n=<optimized out>, data=<optimized out>, f=<optimized out>) at libioP.h:948
#2  __GI__IO_default_xsputn (f=f@entry=0x7fd4bf9336a0 <_IO_2_1_stdout_>, data=<optimized out>, n=n@entry=3) at genops.c:370
#3  0x00007fd4bf7d56fa in _IO_new_file_xsputn (n=3, data=<optimized out>, f=<optimized out>) at fileops.c:1265
#4  _IO_new_file_xsputn (f=0x7fd4bf9336a0 <_IO_2_1_stdout_>, data=<optimized out>, n=3) at fileops.c:1197
#5  0x00007fd4bf7bc972 in __vfprintf_internal (s=0x7fd4bf9336a0 <_IO_2_1_stdout_>, format=0x5556655db011 "lol", ap=ap@entry=0x7fff657394a0, 
    mode_flags=mode_flags@entry=0) at ../libio/libioP.h:948
#6  0x00007fd4bf7a7d3f in __printf (format=<optimized out>) at printf.c:33
#7  0x00005556655da1ab in main () at overm.c:8

上面的显示,在源代码的第 33 行调用时发生了崩溃。让我们在函数上设置一个断点,然后从调试器重新运行程序:printf()printf()

(gdb) br printf
Breakpoint 3 at 0x7ffff7e1ac90: file printf.c, line 28.
(gdb) run
Breakpoint 3, __printf (format=0x555555556011 "lol") at printf.c:28
28  printf.c: No such file or directory.
(gdb) 

一旦我们在调用时停止,在函数上添加另一个断点(我们以前没有这样做,因为在程序启动期间被内部代码调用了几次)并继续执行:printf()malloc()malloc()

(gdb) br malloc
(gdb) continue
Continuing.

Breakpoint 4, malloc (s=140737354002065) at overm.c:4
4   void *malloc(size_t s) {
(gdb) where
#0  malloc (s=140737354002065) at overm.c:4
#1  0x00007ffff7e3ad04 in __GI__IO_file_doallocate (fp=0x7ffff7fa66a0 <_IO_2_1_stdout_>) at filedoalloc.c:101
#2  0x00007ffff7e4aed0 in __GI__IO_doallocbuf (fp=fp@entry=0x7ffff7fa66a0 <_IO_2_1_stdout_>) at libioP.h:948
#3  0x00007ffff7e49f30 in _IO_new_file_overflow (f=0x7ffff7fa66a0 <_IO_2_1_stdout_>, ch=-1) at fileops.c:745
#4  0x00007ffff7e486b5 in _IO_new_file_xsputn (n=3, data=<optimized out>, f=<optimized out>) at libioP.h:948
#5  _IO_new_file_xsputn (f=0x7ffff7fa66a0 <_IO_2_1_stdout_>, data=<optimized out>, n=3) at fileops.c:1197
#6  0x00007ffff7e2f972 in __vfprintf_internal (s=0x7ffff7fa66a0 <_IO_2_1_stdout_>, format=0x555555556011 "lol", ap=ap@entry=0x7fffffffdd90, 
    mode_flags=mode_flags@entry=0) at ../libio/libioP.h:948
#7  0x00007ffff7e1ad3f in __printf (format=<optimized out>) at printf.c:33
#8  0x00005555555551ab in main () at overm.c:8
(gdb) 

当我们在调用时停止时显示的堆栈显示它是源代码中第 33 行调用的结果。malloc()printf()