问题描述
假设我正在使用一些具有功能的C库:
int foo(char* str);
我知道foo()
不会修改str
指向的内存。它只是写得不好,也不必费心声明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++draft basic#link-11:
在对类型进行所有调整之后(在其typedef被其定义替换之前),所有引用给定变量或函数的声明所指定的类型应相同,[...]
因为在C ++翻译单元中具有C语言链接的声明int foo(const char *str)
与在C翻译单元中声明int foo(char *str)
的声明相同(因此具有C语言链接) ),则行为是不确定的(带有著名的“无需诊断”)。
从C端开始(我认为甚至都不需要-C ++端足以使程序具有未定义的行为。),最重要的部分是C99 6.7.5.3p15:
要使两个函数类型兼容,两者都应指定兼容的返回类型。此外,参数类型列表(如果同时存在)应在参数数量和省略号终止符的使用上达成共识;相应的参数应具有兼容的类型[...]
要使两个指针类型兼容,则两者必须具有相同的限定条件,并且都应是指向兼容类型的指针。
要使两个合格的类型兼容,两者都应具有兼容类型的相同合格版本[...]
因此,由于char
与const char
不兼容,因此const char *
与char *
不兼容,因此int foo(const char *)
与{{1}不兼容}。调用这样的函数(C99 6.5.2.2p9)将是未定义的行为(您可能还会看到C99 J.2)
-从实际角度来看:
我不认为能够找到一个编译器与体系结构的组合,其中一个翻译单元看到int foo(char*)
,而另一个翻译单元定义了一个函数int foo(const char *)
,它将“不起作用”。
从理论上讲,疯狂的实现可以使用不同的寄存器传递int foo(char *) { /* some stuff */ }
参数,并使用不同的寄存器传递const char*
参数,我希望可以对此进行详细记录在那种疯狂的架构中,ABI和编译器。如果是这样,错误的寄存器将用于参数,将“不起作用”。
仍然,使用简单的包装程序不会产生任何费用:
char*
,
我认为基本答案是:
是的,如果引用的对象本身是const
,例如示例中的字符串文字,则可以取消const
even 。
仅在尝试修改const
对象而不是强制转换的情况下才指定发生未定义的行为。
这些规则及其存在的原因是“旧的”。我确定他们早于C ++ 98。
将其与volatile
进行对比,其中通过非易失性引用访问的任何尝试都是不确定的行为。我只能在此处读和/或写为“访问”。
我不会重复其他建议,但这是最偏执的解决方案。 这不是因为C ++语义不清楚而引起的偏执。他们很清楚。至少如果您接受某些不确定行为,那就很清楚了!
但是您将其描述为“写得不好”,并且想要在它周围放些沙袋!
偏执狂的解决方案依赖于这样一个事实,即如果您传递一个常量对象,则在整个执行过程中它将是常量(如果程序不会冒用UB的风险)。
因此在调用栈中降低“ hello world”的单个副本,甚至将其初始化为文件作用域对象。您可以在函数中声明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;
}