在外部“ C”声明中使用参数constness进行“播放”是否安全?

问题描述

假设我正在使用一些具有功能的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

要使两个函数类型兼容,两者都应指定兼容的返回类型。此外,参数类型列表(如果同时存在)应在参数数量和省略号终止符的使用上达成共识;相应的参数应具有兼容的类型[...]

由于C99 6.7.5.1p2

要使两个指针类型兼容,则两者必须具有相同的限定条件,并且都应是指向兼容类型的指针。

C99 6.7.3p9

要使两个合格的类型兼容,两者都应具有兼容类型的相同合格版本[...]

因此,由于charconst 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;
}