1

考虑我在动态外部库中具有以下功能libExternal.dylib

void print(char* str)
{
    // Changes the first char to 'a' and prints the string
    *str = 'a';
    printf("%s\n", 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*as 参数。使用时dlsym,我让它得到一个函数,它需要一个const char*. 它有效!

我的问题是,这怎么可能?动态加载器忽略 const 类型?我真的无法在任何地方找到答案,所以请帮助我!:)

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

4

3 回答 3

4

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

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

typedef void(*printFunc)(const char*);
                         ^^^^^^^^^^^

printFunc func = (printFunc)dlsym(hLib, "print");
                 ^^^^^^^^^^^

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

于 2013-09-24T12:20:20.530 回答
1

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

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

C11 草案 6.7.3。6

于 2013-09-24T12:40:05.093 回答
1

考虑这段代码:

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

这相当于您在内部所做的工作。理论上char *c = (char *)b; *c = 'a';是非法的,但实际上它恰好在这种特殊情况下起作用。

将其视为 API 的编写者const和该 API 的用户之间的一种契约,而不是由编译器和运行时严格执行的某种契约。const 存在的第二个原因是它允许将字符串文字放入程序中的只读段中,以进行许多有用的优化(如字符串的重复数据删除),但我认为主要是 const 只是提醒程序员。

这就是为什么在大型项目中我看到将 const 添加到函数参数有时被称为“const 中毒”。你毒化了一些在许多不同的 API 层中传递的字符串或结构,以保证它不会通过这些层的任何地方被修改。添加 const 本身的行为对于发现意外修改发生的位置非常有价值。您总是可以很容易地摆脱 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->string指向的内容foo,但如果我们自己分配它,我们仍然需要能够释放它。语言原教旨主义律师可能会说这是未定义的行为(确实如此),并且有解决方案可以实现相同的目标(有),但这在实践中很常见。

于 2013-09-24T13:24:05.977 回答