将 const char* 从采用 char* 的外部库传递给函数

Passing const char* to function from external library that takes a char*

本文关键字:char 外部 函数 const      更新时间:2023-10-16

考虑我在动态外部库libExternal.dylib中有以下函数:

void print(char* str)
{
    // Changes the first char to 'a' and prints the string
    *str = 'a';
    printf("%sn", str);
}

接下来,我有一个可执行文件,它加载此外部库并调用该函数(省略错误检查(:

int main(int argc, const char * argv[])
{
    void* hLib = dlopen("libExternal.dylib", RTLD_LAZY | RTLD_LOCAL);
    typedef void(*printFunc)(const char*);
    printFunc func = (printFunc)dlsym(hLib, "print");
    std::string test = "hello";
    func(test.c_str());
    dlclose(hLib);
    return 0;
}

如您所见,库中定义的函数将char*作为参数。使用dlsym时,我让它得到一个需要const char*的函数。它有效!

我的问题是,这怎么可能?动态加载程序忽略常量类型?我真的在任何地方都找不到答案,所以请帮助我!:)

编辑:我知道这段代码是错误的,我只是想了解这怎么可能。

它有效,但这并不意味着它是正确的。

它不会忽略 const 类型,而是将外部函数强制转换为接受 const 类型的函数:

typedef void(*printFunc)(const char*);
                         ^^^^^^^^^^^
printFunc func = (printFunc)dlsym(hLib, "print");
                 ^^^^^^^^^^^

并尝试使用正确的函数签名,以避免由于修改 const 值而导致的未定义行为

const char *传递给foo(char *str)是未定义的行为 (UB(。

它可以工作,也可能不工作。 它肯定不适用于阻止写入const内存的系统。 其他系统是宽松的,后续操作可能/可能无法按预期工作。

C11草案6.7.3. 6

请考虑以下代码:

#include <stdio.h>
int
main(int argc, char **argv)
{
        char a[] = "foo";
        const char *b = a;
        char *c = (char *)b;
        *c = 'a';
        printf("%sn", b);
        return 0;
}

这相当于你在内部所做的事情。从理论上讲,char *c = (char *)b; *c = 'a';是非法的,但在实践中,它恰好适用于这种特殊情况。

const视为 API 编写者和该 API 用户之间的一种协定,而不是由编译器和运行时严格强制执行的东西。const 存在的第二个原因是它允许将字符串文字放入程序中的只读段中,这些程序打开了许多有用的优化(如字符串的重复数据删除(,但我认为主要是 const 对程序员来说只是一个提醒。

这就是为什么在大型项目中,我看到将 const 添加到函数参数中有时被称为"const 中毒"。你毒害了一些在许多不同的 API 层中传递的字符串或结构,以保证它不会通过这些层在任何地方被修改。添加 const 本身的行为对于发现发生意外修改的位置非常有价值。你总是可以很容易地摆脱常量并打破合同,但是通过不这样做,你可以更容易地阅读和调试你的程序。我什至做了一些实验,如果编译器希望 const 得到尊重,编译器不会进行合理的优化。

有时甚至有必要出于完全合法的原因抛弃 const。例如,当你有这样的东西时:

struct foo {
    const char *string;
    int dynamic;
};
void
foo_dynamic(struct foo *f, int x)
{
    f->dynamic = 1;
    f->string = malloc(16);
    snprintf(&s->string, 16, "%d", x);
}
void
foo_static(struct foo *f, const char *x)
{
    f->dynamic = 0;
    f->string = x;
}
void
foo_free(struct foo *f)
{
    if (f->dynamic)
        free((void *)f->string);
    free(f);
}

在这种情况下,我们在 API 中承诺我们不会在 foo 的生命周期内更改foo->string指向的内容,但是如果我们自己分配它,我们仍然需要能够释放它。语言原教旨主义律师可能会说这是未定义的行为(确实如此(,并且有解决方案可以实现同样的事情(有(,但这在实践中很常见。