考虑我在动态外部库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*
作为参数。使用dlsym
时,我让它获得了一个const char*
的函数。它有效!
我的问题是,这怎么可能?动态加载器是否忽略const类型?我真的找不到任何答案,所以请帮助我! :)
修改 我知道这段代码是错误的,我只想了解这是怎么回事。
答案 0 :(得分:4)
它有效,但并不意味着它是正确的。
它不会忽略const类型,而是将外部函数转换为接受const类型的函数:
typedef void(*printFunc)(const char*);
^^^^^^^^^^^
printFunc func = (printFunc)dlsym(hLib, "print");
^^^^^^^^^^^
并尝试使用正确的函数签名来避免由于修改const值而导致 未定义的行为 。
答案 1 :(得分:1)
将const char *
传递给foo(char *str)
是未定义的行为(UB)。
它可能有效,但可能没有。它肯定不适用于阻止写入const
内存的系统。其他系统是宽松的,后续操作可能/可能无法按预期工作。
C11草案6.7.3。 6
答案 2 :(得分: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';
是非法的,但实际上它恰好在这种特殊情况下起作用。
将const
视为API的编写者与该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
指向的内容,但我们仍需要能够免费如果我们自己分配它。语言原教旨主义者律师可能会说这是未定义的行为(它是)并且有解决方案可以实现同样的事情(有),但这在实践中非常普遍。