假设我使用的是某个C库,它有一个函数:
int foo(char* str);
并且我知道CCD_ 1不修改CCD_。它只是写得不好,并且没有麻烦声明str
是常量。
现在,在我的C++代码中,我目前有:
extern "C" int foo(char* str);
我是这样使用的:
foo(const_cast<char*>("Hello world"));
我的问题是:原则上,从语言律师的角度来看,在实践中,我写安全吗
extern "C" int foo(const char* str);
跳过const_cast
?
如果不安全,请解释原因。
注意:我对C++98代码的情况特别感兴趣(是的,我很难过),所以如果你假设的是语言标准的更高版本,请这样说。
我写:并跳过const_cast‘ing安全吗?
否。
如果不安全,请解释原因。
来自语言端:
在阅读了dcl.link之后,我认为C和C++之间的互操作性是如何工作的并没有得到确切的说明;不需要诊断";案例。最重要的部分是:
具有C语言链接的函数的两个声明,具有相同的函数名称(忽略限定它的命名空间名称),出现在不同的命名空间作用域中,它们引用了相同的函数。
因为它们引用了相同的函数,我相信一个合理的假设是,在C++端具有C语言链接的标识符的声明必须与在C端的该符号的声明兼容。在C++中存在没有";兼容类型";,在C++中,两个声明必须相同(转换后),这使得限制实际上更加严格。
从C++方面,我们阅读了C++基本草案#link-11:
在对类型进行所有调整后(在此期间,typedef被其定义所取代),所有引用给定变量或函数的声明所指定的类型应相同,[…]
因为C++翻译单元中具有C语言链接的声明int foo(const char *str)
与C翻译单元中声明的声明int foo(char *str)
不相同(因此它具有C语言连接),所以行为是未定义的(著名的"不需要诊断")。
从C端(我认为这甚至是不需要的-C++端足以使程序具有未定义的行为。无论如何),最重要的部分将是C99 6.7.5.3p15:
对于要兼容的两种函数类型,两者都应指定兼容的返回类型。此外,参数类型列表(如果两者都存在)应在参数数量和省略号终止符的使用方面达成一致;相应的参数应具有兼容类型[…]
因为C99 6.7.5.1p2:
对于要兼容的两个指针类型,两者都应具有相同的限定,并且都应是指向兼容类型的指针。
和C99 6.7.3p9:
对于两个合格的兼容类型,两者都应具有兼容类型的相同合格版本[…]
因此,由于char
与const char
不兼容,因此const char *
与foo()
0不兼容,从而int foo(const char *)
与int foo(char*)
不兼容。调用这样的函数(C99 6.5.2.2p9)将是未定义的行为(也可以参见C99 J.2)
--从实用角度来看:
我不相信能够找到编译器+体系结构的组合,其中一个翻译单元看到CCD_;不工作";。
从理论上讲,一个疯狂的实现可能会使用不同的寄存器来传递const char*
参数,而使用另一个寄存器来传递char*
参数,我希望这会在疯狂的体系结构ABI和编译器中得到很好的记录。如果是这样的话,错误的寄存器将被用于参数,它将";不工作";。
尽管如此,使用一个简单的包装器并不需要任何费用:
static inline int foo2(const char *var) {
return foo(static_cast<char*>(var));
}
我认为基本答案是:
是的,如果引用的对象本身是const
(例如本例中的字符串文字),则可以丢弃const
,甚至。未定义的行为仅指定在尝试修改const
对象时出现,而不是由于强制转换。这些规则及其存在的理由是"古老的"。我确信它们早于C++98。
与str
0相比,在volatile
中,通过非易失性引用访问易失性对象的任何尝试都是未定义的行为。我只能在此处将"access"读作read和/或write。
我不会重复其他建议,但这里是最偏执的解决方案。这是偏执的,并不是因为C++语义不清楚。他们很清楚。至少,如果你接受了一些未定义的行为,那就很清楚了!
但你形容它"写得很糟糕",你想在它周围放一些沙袋!
偏执的解决方案依赖于这样一个事实,即如果你传递一个常量对象,它在整个执行过程中都是常量(如果程序不冒UB的风险)。
因此,制作一份";你好世界";在调用堆栈中更低,甚至初始化为文件范围对象。您可以在函数中声明它为static
,并且它将只构造一次(开销最小)。
这几乎恢复了字符串文字的所有优点。包括文件作用域(全局)在内的调用堆栈越低越好。我不知道传递给foo()
的指向对象的生存期需要多长。因此,它至少需要在链中足够低才能满足这个条件。注意:C++98有std::string
,但它在这里不太管用,因为你仍然被禁止修改c_str()
的结果。这里定义了语义。
#include <cstring>
#include <iostream>
class pseudo_const{
public:
pseudo_const(const char*const cstr): str(NULL){
const size_t sz=strlen(cstr)+1;
str=new char[sz];
memcpy(str,cstr,sz);
}
//Returns a pointer to a life-time permanent copy of
//the string passed to the constructor.
//Modifying the string through this value will be reflected in all
// subsequent calls.
char* get_constlike() const {
return str;
}
~pseudo_const(){
delete [] str;
}
private:
char* str;
};
const pseudo_const str("hello world");
int main() {
std::cout << str.get_constlike() << std::endl;
return 0;
}