无法 return 从 C 中的共享库更正内存地址

Cannot return correct memory address from a shared lib in C

我一直在尝试实现一个小型模拟来理解 malloc() 的内存分配。我创建了一个名为 mem.c 的共享库。我正在将库链接到主库,但无法传递模拟“堆”的正确地址。堆由共享库中的 malloc() 调用创建。

共享库中的地址:0x55ddaff662a0 主地址:0xfffffffffaff662a0

只有最后 4 个字节似乎是正确的。其余设置为 0xf.

但是,当我在主体中#include "mem.c" 时,它工作正常。如何在不包括 mem.c 的情况下获得相同的结果。我试图在不包括 mem.c 或 mem.h 的情况下解决这个问题。我这样创建共享库:

gcc -c -fpic mem.c
gcc -shared -o libmem.so mem.o
gcc main.c -lmem -L. -o main

来自您的评论

I am trying to implement without using #include mem.h or mem.c.

那么您必须通过其他方式提供您正在调用的函数的原型。没有明确的函数原型,遵循 K&R 和后来的 ANSI C 的传统,未声明的函数被假定为 return 和 int 并采用 int.

类型的参数

编辑:本质上,在首次使用该功能之前,您需要在 header 中的某处写下您通常会找到的内容。或者它是一个函数指针,你需要一个合适的变量来存储函数指针。

例如,要声明一个 return 是无类型指针的函数,以及您编写的任意未指定数量的参数

void *getAddr();

请注意,此处不需要使用 extern 关键字,因为 extern linkage 始终隐含在 non-static 函数声明中。

如果您想在运行时动态地 link(使用 dlopen / LoadLibrarydlsym / GetProcAddress),您可以定义一个函数指针变量

void* (*getAddr_fptr)();

您可以使用 dlsym

来设置它
*(void**)(&getAddr_fptr) = dlsym(…)

这种笨拙的编写方式是由于允许函数指针具有与数据指针不同的大小和对齐方式(有关详细信息,请参阅 dlsym 联机帮助页)。

目前在大多数平台上 int 是 4 字节类型,最常见的调用约定通过 register 传递前几个函数参数。在 x86(和 x86_64)上,寄存器是 AX、BX、CX 和 DX,可以以不同的大小访问,但可以以不同的大小读取和写入(以允许大小转换)。这解释了为什么只传递前 4 个字节:它通过寄存器传递,并且只有对寄存器的写入是作为 4 字节宽的写入完成的。当函数随后从寄存器读取时,它使用更宽的类型进行读取,并将较高值位设置为全 1。

我假设您正在尝试完成这样的事情?对于 mem.c

#include <stdlib.h>
#include <stdio.h>

void* getAddr() {
    char *heap = (char *)malloc(10);
    printf("%p\n", (void*)heap);
    return heap;
}

然后在不为 mem.c 函数包含任何 headers 的情况下,您可能会从 mem.c 创建一个库,正如您在问题中已经提到的那样,并且有一些东西如下main.c

#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>

typedef void* (*getAddr)(); //prototype for getAddr() in mem.c

int main() {
    void* handle = dlopen("./libmem.so", RTLD_LAZY);
    if(handle) {
        void* fn = dlsym(handle, "getAddr");
        if(fn) {
            void* addr = ((getAddr)(fn))();
            printf("%p\n", addr);
            free(addr);
            addr = NULL;
        } else {
            printf("Failed to dlsym %s\n", dlerror());
        }
    } else {
        printf("Failed to dlopen %s\n", dlerror());
    }
}

编辑:出于@Zilog80 提到的 OP 目的,由于库正在与 main 可执行文件链接,因此可以删除 dlopen() 部分并简化 main.c作为

#include <stdio.h>
#include <stdlib.h>

extern void* getAddr(); //prototype for getAddr() in mem.c

int main() {
    void* addr = getAddr();
    printf("%p\n", addr);
    free(addr);
    addr = NULL;
}

并使用与 OP 类似的编译命令,即

gcc -shared -o libmem.so -fpic mem.c
gcc main.c -lmem -L . -o main

正在执行

LD_LIBRARY_PATH=. ./main

来自评论:

Do you have a declaration for getAddr in your main code?

不,我没有,但我想在没有声明的情况下实施,这可能吗?

那就是你的问题了。如果没有声明,编译器将回退到默认声明 int getAddr()。这与 return 是 void * 的实际定义不兼容,并且通过不兼容的声明调用函数会触发 undefined behavior.

可能发生的事情是,当函数的 return 值实际上是 returned 时,您只取回了 4 个低位字节。假设您的系统是小端,int 是 4 个字节,void * 是 8 个字节,这可以解释低位是相同的。

必须在调用函数之前包含有效的声明。它不一定必须驻留在头文件中,但必须在调用发生时可见。