【问题标题】:c++ floats and valgrind strange behaviourc++ floats 和 valgrind 奇怪的行为
【发布时间】:2012-09-12 22:36:51
【问题描述】:

我有 valgrind 3.6.0,我到处搜索,一无所获。

问题是,当我在使用 valgrind 时尝试访问浮点数时,会出现段错误,但是当我按原样运行程序时,没有 valgrind,一切都会按预期进行。

这是一段代码:

class MyClass {
    public:
    void end() {
        float f;
        f = 1.23;
        std::stringstream ss;
        ss << f;
        std::cout << ss.str();
    }
};

extern "C" void clean_exit_on_sig(int sig) {
    //Code logging the error
    mc->end();
    exit(1);
}

MyClass *mc;
int main(int argc, char *argv[]) {
    signal(SIGINT , clean_exit_on_sig);
    signal(SIGABRT , clean_exit_on_sig);
    signal(SIGILL , clean_exit_on_sig);
    signal(SIGFPE , clean_exit_on_sig);
    signal(SIGSEGV, clean_exit_on_sig);
    signal(SIGTERM , clean_exit_on_sig);
    mc = new MyClass();
    while(true) {
        // Main program loop
    }
}

当我按下 Control+C 时,程序正确捕获信号并且一切正常,但是当我使用 valgrind 运行程序时,当尝试执行此命令 ss &lt;&lt; f; // (Inside MyClass) 时会抛出段错误:-/

我也试过这个:

std::string stm = boost::lexical_cast<std::string>(f);

但是当 boost 也访问浮点数时,我继续收到一个段错误信号。

这是我使用 boost 出现段错误时的回溯:

./a.out(_Z17clean_exit_on_sigi+0x1c)[0x420e72]
/lib64/libc.so.6(+0x32920)[0x593a920]
/usr/lib64/libstdc++.so.6(+0x7eb29)[0x51e6b29]
/usr/lib64/libstdc++.so.6(_ZNKSt7num_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE15_M_insert_floatIdEES3_S3_RSt8ios_baseccT_+0xd3)[0x51e8f43]
/usr/lib64/libstdc++.so.6(_ZNKSt7num_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE6do_putES3_RSt8ios_basecd+0x19)[0x51e9269]
/usr/lib64/libstdc++.so.6(_ZNSo9_M_insertIdEERSoT_+0x9f)[0x51fc87f]
./a.out(_ZN5boost6detail26lexical_stream_limited_srcIcSt15basic_streambufIcSt11char_traitsIcEES4_E9lcast_putIfEEbRKT_+0x8f)[0x42c251]
./a.out(_ZN5boost6detail26lexical_stream_limited_srcIcSt15basic_streambufIcSt11char_traitsIcEES4_ElsEf+0x24)[0x42a150]
./a.out(_ZN5boost6detail12lexical_castISsfLb0EcEET_NS_11call_traitsIT0_E10param_typeEPT2_m+0x75)[0x428349]
./a.out(_ZN5boost12lexical_castISsfEET_RKT0_+0x3c)[0x426fbb]
./a.out(This line of code corresponds to the line where boost tries to do the conversion)

这是默认的字符串流转换:

./a.out(_Z17clean_exit_on_sigi+0x1c)[0x41deaa]
/lib64/libc.so.6(+0x32920)[0x593a920]
/usr/lib64/libstdc++.so.6(+0x7eb29)[0x51e6b29]
/usr/lib64/libstdc++.so.6(_ZNKSt7num_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE15_M_insert_floatIdEES3_S3_RSt8ios_baseccT_+0xd3)[0x51e8f43]
/usr/lib64/libstdc++.so.6(_ZNKSt7num_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE6do_putES3_RSt8ios_basecd+0x19)[0x51e9269]
/usr/lib64/libstdc++.so.6(_ZNSo9_M_insertIdEERSoT_+0x9f)[0x51fc87f]
./a.out(This line of code corresponds to the line where I try to do the conversion)

a.out 是我的程序,我以这种方式运行 valgrind:valgrind --tool=memcheck ./a.out

另一个奇怪的事情是,当我在程序运行正常时调用mc-&gt;end();(收到任何信号,Object 刚刚完成他的工作),我不会以任何方式出现段错误(就像 valgrind 一样)。

请不要告诉我“不要用 Control+C 等等等等来关闭你的程序……”这段代码用于记录程序可能出现的任何错误,而不会在发生段错误的情况下丢失数据,杀死它因为死锁或其他原因。

编辑:也许是一个 valgrind 错误(我不知道,在 google 上搜索但什么也没找到,不要杀我),也可以接受任何解决方法。

EDIT2:刚刚意识到boost也调用ostream(这里比使用vim更清楚:-/),准备尝试sprintf float转换。

EDIT3:试过这个sprintf(fl, "%.1g", f);但仍然崩溃,回溯:

./a.out(_Z17clean_exit_on_sigi+0x40)[0x41df24]
/lib64/libc.so.6(+0x32920)[0x593a920]
/lib64/libc.so.6(sprintf+0x56)[0x5956be6]
./a.out(Line where sprintf is)

【问题讨论】:

  • 我不认为您的信号处理程序是异步安全的。不允许在信号处理程序中做复杂的事情。
  • 解决方法:将printf("%f", f); 放在您的信号处理程序中。
  • Kerrek SB,我知道,但没有 valgrind 一切正常,所以我认为我没有做复杂的事情,因为工作。我需要将浮点数放在一个字符串中并尝试这样做:sprintf(fl, "%.1g", f); 但在访问浮点数时仍然在同一行崩溃,所以我认为这是浮点数的问题。我想我要把它乘以 10 并将其转换为一个整数。
  • “一切正常”是一条红鲱鱼。 UB 就是 UB,即使它看起来“不错”。
  • (您的信号处理程序上还缺少extern "C"。)

标签: c++ floating-point segmentation-fault valgrind stringstream


【解决方案1】:

好的,经过几个小时的阅读和研究,我发现了问题,我将回答我自己的问题,因为没有人回答,只有@Kerrek SB [https://stackoverflow.com/users/596781/kerrek-sb] 的评论,但我不能接受评论。 (谢谢)

就像在信号处理程序中一样简单,您只能安全地调用一堆函数:http://pubs.opengroup.org/onlinepubs/009695399/functions/xsh_chap02_04.html

如果您调用一些非异步安全的函数,它们可以工作,但并非总是如此。

如果您想在信号处理程序中调用非异步安全函数,您可以这样做:

  • 创建 2 个管道。 int pip1[2]; int pip2[2]; pipe(pip1); pipe(pip2);
  • 创建一个新线程并让线程等待从第一个管道read(pip1[0], msg, 1);接收一些数据
  • 调用信号处理程序时,使用write异步安全函数写入第一个管道write(pip1[1], "0", 1);
  • 然后用read(pip2[0], msg, 1); 让信号等待第二个管道
  • 线程将唤醒并完成他必须做的所有工作(在这种情况下将数据保存到数据库),然后让线程将数据写入第二个管道write(pip2[1], "0", 1);
  • 现在主线程将唤醒并以_Exit(1) 或其他内容结束。

信息:

我使用 2 个管道,因为如果我写入一个管道,然后我读取它,第二个线程可能永远不会唤醒,因为主线程已经读取了刚刚写入的数据。而且我正在使用辅助管道来阻塞主线程,因为我不希望它在第二个线程正在保存数据时退出。

请记住,信号处理程序可能在修改共享资源时被调用,如果您的第二个线程访问该资源,您可能会遇到第二个段错误,因此在使用您的第二个线程访问共享资源时要小心(全局变量或别的东西)。

如果您正在使用 valgrind 进行测试并且不想在接收到信号时收到“错误”内存泄漏,您可以在退出 pthread_join(2ndthread, NULL)exit(1) 而不是 _Exit(1) 之前执行此操作。这些是非异步安全的函数,但至少您可以测试内存泄漏并使用信号关闭您的应用,而不会收到“假”内存泄漏。

希望这对某人有所帮助。再次感谢@Kerrek SB。

【讨论】:

    【解决方案2】:

    调试器和其他东西有时会向您通常不会收到的进程发送信号。例如,我不得不更改使用 recv 的函数在 gdb 下工作。检查您的信号是什么,并在尝试使用它之前验证 mc 不为空。看看这是否开始让你更接近答案。

    我在想,也许您使用 new(或其他东西)可能会导致 valgrind 在初始化 mc 之前发送一个被您的处理程序捕获的信号。

    很明显,您没有粘贴实际代码,因为您使用“类”而不公开 end() 函数意味着这不应该编译。

    【讨论】:

    • 是的,我已经检查过了,mc 不为空。是的,这不是完整的代码,因为代码现在有几千行,所以不可能放真正的代码,只是忘了添加public:,准备编辑它。仅当某些代码尝试访问浮点时才发送信号,因此new 没有抛出任何信号,它按预期工作。
    猜你喜欢
    • 1970-01-01
    • 2017-08-10
    • 2012-11-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多