为什么我的字符串赋值会导致分段错误?

3
我已经尝试消除诸如解引用空指针等段错误的常见原因,但我束手无策。在我的测试机上,此错误不会出现在调试模式下,但在我生产机器的发布编译中会出现。两者都设置为/O0以排除这种可能性。当我注释掉字符串赋值时,段错误消失了,现在我只需要理解发生了什么,以便我可以修复它。
可能有更明显的东西,但进一步的复杂性是,此代码是由内核模式应用程序调用的,而我从供应商收到的关于编写系统安全代码的唯一指导是“每个需要操作系统任何内容或必须等待任何内容来自操作系统的函数将无法工作”(逐字翻译)。
union {
    REAL_T real[8];
    char   byte[64];
} fileName;

void transferFilenames () {
    string tempname;

    // This is from an api I must use, it retrieves values from NVRAM on
    // an accessory board.  Specifically it will return 8 REAL_T values
    // and store them starting at &filename.real[0], inp/outpArray are 
    // globals defined elsewhere.
    inpArray.I7_ARRAY.INDEX = 2903;
    inpArray.I7_ARRAY.LEN = 8;
    inpArray.I7_ARRAY.Z_PAR_PTR = &fileName.real[0];
    b_array ( READ_CYCLE_PARAMS, &status, &inpArray, &outpArray );

    // if status != 0, there was an error
    if ( status == 0 ) {
        // there is no guarantee of being null terminated
        fileName.byte[63] = 0;

        /* This test code didn't fix the problem
        int i;
        char myStr[64];
        for ( i = 0; i < 64; i++ )
            myStr[i] = fileName.byte[i];
        if ( myStr[0] != '\0' )
            string mystring( myStr ); // seg fault
        */
        tempname.assign( fileName.byte ); // Throws seg fault
        // tempname.assign( &fileName.byte[0] ); // try to be more explicit

        // controlBlock is a global class defined elsewhere
        controlBlock->setFileName ( tempname, ISINPUT);
    } else {
        controlBlock->setFileName( "BAD", ISINPUT );
    }
    return;
}

当我过载控制块以直接使用char* controlBlock->setFileName( &fileName.byte[0] ),并完全删除字符串赋值时,分段错误消失了。 过载的所有内容都只是将char*分配给本地字符串并调用常规方法。

我在幕后错过了什么?


@MarkB 当程序崩溃时,没有转储文件被创建。实时系统不支持实时调试,而宽松时间系统则不会出现此错误。 - Stephen
1
你在其他地方也使用了std::string吗?它会在堆上分配内存,而不是栈。 - murrekatt
添加到murrekatt的评论和我的回答后:子句“每个需要操作系统的任何内容或必须等待操作系统的任何内容的函数都将无法工作”可能会阻止您使用C ++库的大块。相关系统很可能沿着malloc(size_t) {kill(0, SEGBUS);}的路线实现malloc - David Hammen
我在这段代码的很少部分使用了C++库,因此将其重构为纯C并非不可能。 - Stephen
即使没有C++模板库,C++仍然可以提供比C更多的功能。 - David Hammen
显示剩余2条评论
2个回答

3
问题可能不在调用tempname.assign()上。流水线处理可能导致对恶魔的位置出现某些错误的报告。这句话让我想到恶魔在别的地方:
当我重载控制块以直接使用char *控制块 - > setFileName(&fileName.byte [0] ),并完全删除字符串赋值时,分段错误消失了。
在一个情况下,您向setFileName传递一个std :: string,在另一个情况下,传递一个char *。尝试替换
controlBlock->setFileName ( tempname, ISINPUT);

使用

controlBlock->setFileName ( tempname.c_str(), ISINPUT);

补充说明
问题很可能是使用 std::string,当保留大小太小时,std::string::assign() 将调用 malloc。使保留大小足够大也不起作用; 这可能只会将 malloc 调用推入构造函数中。使用一些动态分配内存的东西与条款 每个需要操作系统任何内容或必须等待来自操作系统的任何内容的函数都无法正常工作 不符。

实际上,在这台机器上,这个条款甚至可能排除了使用 C++ 库的大量代码。C++ 库在分配和释放内存方面非常宽松,而 std::string 则会频繁地进行此类操作。


使用tempname.c_str()仍然引发了段错误。 - Stephen
我也同意问题可能不在assign()函数中,但这是我能够缩小重现错误范围的唯一代码行。 - Stephen
鉴于您在其他地方的评论,您是否存在时间错误? 您是否需要以某种方式等待“b_array”完成其工作? 另一件需要注意的事情是,设置联合体的一个成员然后访问另一个成员,在严格意义上是非法的(这是未定义行为)。尽管这个技巧在大多数机器和编译器上都有效,但总有那么一只奇怪的野兽它不起作用。也许您已经抓住了这样的野兽的尾巴。 - David Hammen
2
最后一个想法:std::string::assign() 将会调用 malloc。这是安全的吗,特别是考虑到“每个需要操作系统任何东西或必须等待操作系统任何东西的函数 都将不起作用”? - David Hammen
我不需要等待b_array(它是一个阻塞调用)。你关于字符串赋值内部的malloc的评论可能很好地解释了这个问题。默认情况下我们得到15个字符,所以我尝试分配一些更小和更大的东西。更小的不会产生错误,但更大的会产生错误。 - Stephen

1
程序是否使用与您必须使用的API相同的编译器和libstdc++头文件进行编译?或者是用调试程序调用发布库?
std::string在头文件中被完全定义为一个模板。这意味着您将构建编译器对std::string的了解,并将其传递给API,而库将根据自己对std::string的理解来解释它。当双方的实现不兼容时,这有时会导致问题,并且不建议为库暴露接受std::string的API。为了避免问题,您确实应该将char数组传递给期望std::string的任何库。
在Gcc5+中使用或不使用_GLIBCXX_USE_CXX11_ABI也可能影响代码中某些部分对std::string的实现方式。
我知道这是一个旧问题,但它有时会成为答案中的首要问题,所以新用户应该了解这个可能的原因。

我的问题是不可见的malloc调整字符串大小(实时系统中,禁止使用malloc调用),但感谢答案,这可能对未来的访问者有用。 - Stephen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接