14

对于我的 Programming 102 课程,我们被要求提供可在 Linux 下编译和运行的 C 代码。我的硬盘上没有足够的空闲空间来安装 Linux 和 Windows,所以我使用 cygwin 来编译我的程序。

我必须提供的最新程序在 cygwin 下编译并运行良好。它在 Linux 下编译良好,但执行到一半会产生分段错误。我向给我们上课的研究生解释了这一点,他说 cygwin 的 GCC 版本允许编译和执行更草率的代码。

我通过谷歌找到的少数参考资料还没有定论。我发现的一个线程说Linux下seg错误的原因是内存泄漏。为什么这不会影响 cygwin 版本?

我会使用大学的计算机,但我不能在它们上使用 Subversion,这会严重阻碍我的努力。(我是编码新手,经常需要能够恢复到 X 之前的版本)。

cygwin 的 GCC 版本对它编译的代码真的更“宽松”吗?如果是这样,编码时是否有任何明显的问题需要注意?有没有其他方法可以编写在 Linux 下运行的代码?

编辑

感谢您的回复。我在原始帖子中不够明确:我的代码中有一个错误对我来说几乎是给定的(我对编程很陌生,毕竟在谈到 C 时真的很绿色)。我的 TA 暗示 cygwin 的 GCC 是一个不太可靠的编译器 - 允许运行更草率的代码 - 比在 GNU/Linux 下找到的编译器要低。我发现这很奇怪,所以在互联网上进行了搜索,但实际上找不到任何关于该事实的参考资料。

除了将编译器与我的代码归咎于我之外,我还想知道程序在 Windows 下运行并在 Linux 下崩溃的原因是什么。回复:Windows/Linux 下的不同内存管理器和堆/堆栈布局在这方面进行了说明。

cygwin 的 GCC 与 GNU/Linux 一样“好”的结论会不会非常正确?

关于发布源代码,这是一项家庭作业,所以如果可能的话,我宁愿自己找到问题:)

编辑 2

我已经接受了 jalf 的回答,因为它谈到了是什么让程序在 Windows 下而不是在 Linux 下运行,这是我真正想知道的。感谢所有做出贡献的其他人,他们都是非常有趣且内容丰富的回复。

当我发现问题并修复它时,我将上传一个包含这个非工作版本的所有源代码的 zip 文件,以防有人好奇我到底做了什么:)

编辑 3

对于那些有兴趣看代码的人,我发现了问题,并且确实是由于指针。我试图从函数返回一个指针。我试图返回的指针是在函数内部声明的,所以一旦函数执行就被销毁。有问题的代码在第 22-24 行被注释掉。

随意嘲笑我的代码。

/**
*  Returns array of valid searches based on current coordinate
*/
void determine_searches(int row, int col, int last_row, int last_col, int *active_search){
    // define coordinate categories and related valid search directions
    int Library0[] = {2, 3, 4, -1};
    int Library1[] = {4, 5, 6, -1};
    int Library2[] = {2, 3, 4, 5, 6, -1};
    int Library3[] = {0, 1, 2, 3, 4, 5, 6, 7, -1};
    int Library4[] = {0, 1, 2, -1};
    int Library5[] = {0, 6, 7, -1};
    int Library6[] = {0, 1, 2, 6, 7, -1};
    int Library7[] = {0, 1, 2, 3, 4, -1};
    int Library8[] = {0, 4, 5, 6, 7, -1};

    int * Library[] = { 
        Library0, Library1, Library2,
        Library3, Library4, Library5,
        Library6, Library7, Library8,
    };

    // declare (and assign memory to) the array of valid search directions that will be returned
    //int *active_search;
    //active_search = (int *) malloc(SEARCH_DIRECTIONS * sizeof(int));


    // determine which is the correct array of search directions based on the current coordinate
    // top left corner
        int i = 0;
    if(row == 0 && col == 0){
        while(Library[0][i] != -1){
            active_search[i] = Library[0][i];
            i++;
        }
    }
    // top right corner
    else if(row == 0 && col == last_col){
        while(Library[1][i] != -1){
            active_search[i] = Library[1][i];
            i++;
        }
    }
    // non-edge columns of first row
    else if(row == 0 && (col != 0 || col != last_col)){
        while(Library[2][i] != -1){
            active_search[i] = Library[2][i];
            i++;
        }
    }
    // non-edge coordinates (no edge columns nor rows)
    else if(row != 0 && row != last_row && col != 0 && col != last_col){
        while(Library[3][i] != -1){
            active_search[i] = Library[3][i];
            i++;
        }
    }
    // bottom left corner
    else if(row == last_row && col == 0){
        while(Library[4][i] != -1){
            active_search[i] = Library[4][i];
            i++;
        }
    }
    // bottom right corner
    else if(row == last_row && col == last_col){
        while(Library[5][i] != -1){
            active_search[i] = Library[5][i];
            i++;
        }
    }
    // non-edge columns of last row
    else if(row == last_row && (col != 0 || col != last_col)){
        while(Library[6][i] != -1){
            active_search[i] = Library[6][i];
            i++;
        }
    }
    // non-edge rows of first column
    else if((row != 0 || row != last_row) && col == 0){
        while(Library[7][i] != -1){
            active_search[i] = Library[7][i];
            i++;
        }
    }
    // non-edge rows of last column
    else if((row != 0 || row != last_row) && col == last_col){
        while(Library[8][i] != -1){
            active_search[i] = Library[8][i];
            i++;
        }
    }
    active_search[i] = -1;
}
4

11 回答 11

14

我并不是说听起来粗鲁,但可能是你的代码不好,而不是编译器。;) 这样的问题实际上比你想象的更常见,因为不同的操作系统和编译器将有不同的方式来组织你的应用程序在堆栈和堆中的数据。前者可能特别成问题,特别是如果您最终覆盖堆栈上的内存,或者引用系统决定用于其他用途的已释放内存。所以基本上,有时你可能会侥幸逃脱,但有时你的应用程序会窒息而死。无论哪种方式,如果它出现段错误,那是因为您试图引用不允许的内存,所以它没有在另一个系统/编译器下崩溃更像是一个“幸运的巧合”。

但实际上,段错误就是段错误,因此您应该调试代码以查找内存损坏,而不是调整编译器的配置以找出问题所在。

编辑:好的,我明白你现在的意思了......我以为你是用“X 很糟糕,但 Y 工作得很好!”来解决这个问题的。态度,但似乎是你的TA得到了那个。;)

无论如何,这里有一些调试问题的提示:

  • 寻找指针算术,引用/取消引用可能的“doh!” 错误。您添加/减去一个(又名,fencepost 错误)的任何地方都特别值得怀疑。
  • 注释掉问题区域周围对 malloc/free 的调用,以及使用这些指针的任何相关区域。如果代码停止崩溃,那么您正朝着正确的方向前进。
  • 假设您至少确定了代码崩溃的一般区域,请在其中插入早期返回语句并找到代码不会崩溃的点。这有助于在该点和代码实际崩溃的地方之间找到一个区域。请记住,像这样的段错误不一定会直接发生在您的错误所在的代码行。
  • 使用系统上可用的内存调试工具。
    • 在 Unix 上,查看本指南以在 unix 上调试内存,以及valgrind 分析器(@Sol,谢谢提醒我这个)
    • 在 Visual Studio/Windows 上,你的好伙伴 CrtCheckMemory()非常方便。另外,请阅读CRT 内存调试模式,因为它们是在 VS 中工作的更好功能之一。通常,一旦你记住了各种模式,只需在 VS 中打开一个内存选项卡就足以诊断出这样的错误。
    • 在 Mac OSX 中,您可以在 malloc_error_break(来自 gdb 或 Xcode)上设置断点,这会导致调试器在 malloc 检测到内存损坏时中断。我不确定这是否适用于其他 unix 风格,但快速的谷歌搜索似乎表明它仅限于 mac。此外,对于 OSX 似乎存在一个相当“实验性”的 valgrind 版本。
于 2009-02-09T22:21:20.647 回答
9

就像其他人所说的那样,您可能想在此处发布一些代码,即使这不是您问题的真正重点。让这里的每个人浏览您的代码并查看他们是否能找到导致段错误的原因可能仍然是一个很好的学习体验。

但是,是的,问题在于影响 C 程序的因素太多了,依赖于平台,而且基本上是随机的。虚拟内存意味着有时,访问未分配的内存似乎会起作用,因为您遇到了在较早时间分配的页面的未使用部分。其他时候,它会出现段错误,因为您点击了一个根本没有分配给您的进程的页面。这真的是无法预测的。这取决于您的内存分配在哪里,是在页面边缘还是在中间?这取决于操作系统和内存管理器,以及到目前为止已经分配了哪些页面,以及......你明白了。不同的编译器、相同编译器的不同版本、不同的操作系统、系统上安装的不同软件、驱动程序或硬件,无论您访问未分配内存时是否出现段错误,任何事情都可能发生变化。

至于 TA 声称 cygwin 更“宽松”,这是垃圾,原因很简单。两个编译器都没有发现这个错误!如果“本机”GCC 编译器真的不那么松懈,它会在编译时给你一个错误。段错误不是由编译器生成的。编译器无法确保您获得段错误而不是看似有效的程序。

于 2009-02-10T01:37:26.463 回答
4

我没有听说过 Cygwin 下关于 GCC 怪异的任何具体信息,但在你的情况下,使用 gcc 的 -Wall 命令行选项来显示所有警告可能是个好主意,看看它是否找到任何可能导致代码中的段错误。

于 2009-02-09T22:20:39.290 回答
4

您的代码中肯定有错误。Windows 内存管理器可能比 Linux 内存管理器更宽松。在 Windows 上,您可能会在内存方面做坏事(如覆盖数组边界、内存泄漏、双重释放等),但它会让您侥幸逃脱。与此相关的一个著名故事可以在http://www.joelonsoftware.com/articles/APIWar.html(在那篇(有点长的)文章上搜索“SimCity”)。

于 2009-02-09T22:23:09.347 回答
2

Cygwin 的 gcc 版本可能有其他默认标志和调整设置(例如 wchar_t 为 2 个字节),但我怀疑它对代码特别“松懈”,即使如此 - 你的代码不应该崩溃。如果是这样,那么很可能您的代码中存在需要修复的错误。例如,您的代码可能取决于 wchar_t 的特定大小,或者可能执行根本无法保证工作的代码,例如写入字符串文字。

如果您编写干净的代码,那么它也可以在 linux 上运行。我目前正在运行 firefox 和 KDE 桌面,它们一起由数百万行 C++ 行组成,我没有看到这些应用程序崩溃:)

我建议您将代码粘贴到您的问题中,以便我们查看问题所在。

同时,您可以在gdblinux 调试器中运行您的程序。您还可以在启用所有挡泥板检查并启用所有警告的情况下进行编译。mudflaps 在运行时检查您的代码是否存在各种违规行为:

[js@HOST2 cpp]$ cat mudf.cpp
int main(void)
{
  int a[10];
  a[10] = 3;  // oops, off by one.
  return 0;
}
[js@HOST2 cpp]$ g++ -fmudflap -fstack-protector-all -lmudflap -Wall mudf.cpp
[js@HOST2 cpp]$ MUDFLAP_OPTIONS=-help ./a.out
  ... showing many options ...
[js@HOST2 cpp]$ ./a.out 
*******                 
mudflap violation 1 (check/write): time=1234225118.232529 ptr=0xbf98af84 size=44
pc=0xb7f6026d location=`mudf.cpp:4:12 (main)'                                   
      /usr/lib/libmudflap.so.0(__mf_check+0x3d) [0xb7f6026d]                    
      ./a.out(main+0xb9) [0x804892d]                                            
      /usr/lib/libmudflap.so.0(__wrap_main+0x4f) [0xb7f5fa5f]                   
Nearby object 1: checked region begins 0B into and ends 4B after                
mudflap object 0x9731f20: name=`mudf.cpp:3:11 (main) int a [10]'                
bounds=[0xbf98af84,0xbf98afab] size=40 area=stack check=0r/3w liveness=3        
alloc time=1234225118.232519 pc=0xb7f5f9fd                                      
number of nearby objects: 1                                                     
*** stack smashing detected ***: ./a.out terminated                             
======= Backtrace: =========
....

您可以执行许多挡泥板检查,上面使用默认选项运行 a.out。另一个有助于解决此类错误的工具是valgrind,它还可以帮助您找到泄漏或因上述错误而关闭。将环境变量“MALLOC_CHECK_”设置为 1 也会打印违规消息。malloc有关该变量的其他可能值,请参见手册页。

要检查程序崩溃的位置,您可以使用gdb

[js@HOST2 cpp]$ cat test.cpp
int main() {
    int *p = 0;
    *p = 0;
}
[js@HOST2 cpp]$ g++ -g3 -Wall test.cpp
[js@HOST2 cpp]$ gdb ./a.out
...
(gdb) r
Starting program: /home/js/cpp/a.out

Program received signal SIGSEGV, Segmentation fault.
0x080483df in main () at test.cpp:3
3           *p = 0;
(gdb) bt
#0  0x080483df in main () at test.cpp:3
(gdb)

使用 -g3 编译代码以包含许多调试信息,因此 gdb 可以帮助您找到程序崩溃的精确行。所有上述技术同样适用于 C 和 C++。

于 2009-02-09T22:21:37.277 回答
2

这几乎可以肯定是指针错误或缓冲区溢出,可能是未初始化的变量。

未初始化的指针通常不会指向任何东西,但有时它会指向某个东西;读取或写入它通常会使程序崩溃,但它可能不会。

从释放的记忆中写作或阅读是同一个故事。你可能会侥幸逃脱,然后又可能不会。

这些情况取决于堆栈、堆的布局方式以及运行时在做什么。很可能制作一个在一个编译器/运行时组合而不是另一个编译器/运行时组合上工作的坏程序,仅仅是因为它覆盖了一些无关紧要的东西(这么多),或者一个未初始化的变量“发生”包含一个有效的使用它的上下文的值。

于 2009-02-09T22:35:21.203 回答
1

GCC 的版本可能不是问题。更有可能是运行时库中的差异和代码中的错误,当针对 Windows 版本的运行时运行时,该错误不会表现出来。如果您想要更具体的答案,您可能想要发布段错误的代码和更多背景信息。

一般来说,最好在您将用于运行代码的环境下进行开发。

于 2009-02-09T22:21:36.497 回答
1

您是否做出任何特定于平台的假设,例如数据类型的大小、 s中的数据结构对齐struct字节序

于 2009-02-09T22:28:23.903 回答
1

分段错误意味着您尝试访问无法访问的内存,这通常意味着您尝试取消引用空指针或双重删除内存或获得野指针。有两个原因可以说明您在 cygwin 而不是 Linux 上似乎很好:要么是内存管理器的问题,要么您在其中一个上比另一个更幸运。几乎可以肯定你的代码有错误。

要解决此问题,请查看您的指针使用情况。考虑用智能指针代替原始指针。考虑在之后立即搜索删除并将指针归零(尝试删除空指针是安全的)。如果你能破解 Linux,尝试通过 gdb 获取堆栈跟踪,看看它发生的那一行是否有任何明显的错误。检查您如何使用所有未初始化的指针。如果您可以访问内存调试工具,请使用它。

于 2009-02-09T22:39:26.077 回答
1

一些提示:

  1. 发布您的代码。我敢打赌你会得到一些好的输入,这将使你成为一个更好的程序员。

  2. 使用该选项打开警告-wall并更正报告的任何问题。同样,它可以帮助您成为更好的程序员。

  3. 使用调试器单步执行代码。除了帮助您了解问题所在之外,它还将帮助您成为更好的程序员。

  4. 继续使用 Subversion 或其他源代码控制系统。

  5. 在确定您已经确定问题所在之前,切勿责怪编译器(或操作系统或硬件)。即便如此,也要怀疑自己的代码。


Linux 上的 GCC 的源代码与 Cygwin 上的 GCC 相同。由于 Cygwin POSIX 仿真层和底层 Windows API,平台之间存在差异。额外的层可能比底层硬件更宽容,但这不能指望。

由于这是家庭作业,我会说发布代码是一个更好的主意。有什么比从专业程序员那里获得输入更好的学习方式呢?但是,我建议您在附近的评论中记录您实施的任何建议。

于 2009-02-09T22:49:18.003 回答
1

分段错误是在不存在(或先前已释放)的地址访问内存的结果。我发现非常有趣的是代码在 cygwin 下没有段错误。这可能意味着您的程序使用了指向其他进程地址空间的野指针并且实际上能够读取它(喘气),或者(更有可能)直到程序在 Linux 下运行才到达实际导致段错误的代码.

我推荐以下内容:

  1. 粘贴您的代码,因为这是一个非常有趣的问题
  2. 将此副本发送给 cygwin 开发人员
  3. 如果您需要制作更多在 Linux 下运行的程序,请购买便宜的 Linux VPS,它会让您的生活更轻松。

一旦您在 Linux 下工作(即,在您的 VPS 中运行),请尝试使用以下程序:

  • 广发银行
  • 瓦尔格林德
  • 跟踪

此外,您可以尝试使用诸如电子围栏之类的库来捕捉程序运行时发生的这类事情。

最后,确保将 -Wall 传递给 gcc,您需要它传达的警告。

于 2009-02-10T01:52:06.833 回答