在Windows中使用cygwin编译的C程序,在Linux下进行分段故障。 cygwin GCC’糟糕’吗?

对于我的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; } 

就像其他人所说的那样,你可能想在这里发布一些代码,即使这不是你问题的真正意义。 让每个人都来浏览你的代码,看看他们是否能找到导致段错误的原因,这可能仍然是一个很好的学习经历。

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

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

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

但实际上,段错误是一个段错误,所以你应该调试你的代码寻找内存损坏而不是调整编译器的配置来找出出错的地方。

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

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

  • 寻找指针算术,引用/解除引用可能的“doh!” 错误。 你添加/减去一个(也就是fencepost错误)的任何地方都特别可疑。
  • 注释掉问题区域周围的malloc / free调用,以及使用这些指针的任何相关区域。 如果代码停止崩溃,那么你就会朝着正确的方向前进。
  • 假设您至少已经确定了代码崩溃的一般区域,请在其中插入早期的return语句,并找到代码不会崩溃的点。 这有助于找到该点与代码实际崩溃之间某处的区域。 请记住,像这样的段错误可能不一定直接发生在你的bug所在的代码行上。
  • 使用系统上提供的内存调试工具。
    • 在Unix上,查看本指南在unix上调试内存 ,以及valgrind profiler (@Sol,thx,提醒我这个)
    • 在Visual Studio / Windows上,您的好朋友CrtCheckMemory()非常方便。 另外,请阅读CRT内存调试模式 ,因为它们是在VS中工作的更好的function之一。 通常情况下,在VS中打开一个内存选项卡就足以在记忆各种模式后诊断出这样的错误。
    • 在Mac OSX中,您可以在malloc_error_break(来自gdb或Xcode)上设置断点,这会导致调试器在malloc检测到内存损坏时中断。 我不确定这是否可用于其他unix版本,但快速谷歌搜索似乎表明它只是mac-only。 此外, OSX似乎存在一个相当“实验性”的valgrind版本。

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

你的代码中肯定有一个bug。 Windows内存管理器可能比Linux内存管理器更松懈。 在Windows上,你可能会使用内存做坏事(比如覆盖数组边界,内存泄漏,双重释放等等),但它可以让你逃脱它。 有关这方面的着名故事可以在http://www.joelonsoftware.com/articles/APIWar.html找到(在那篇(有点冗长的)文章中搜索“SimCity”)。

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

一个未初始化的指针通常指向任何东西,但有时它会指向某个东西; 从中读取或写入它通常会使程序崩溃,但话又说不了。

从释放的记忆中写作或阅读是同一个故事; 你可能会逃避它,然后再也许不会。

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

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

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

我建议你将代码粘贴到你的问题中,这样我们就可以看出出了什么问题。

在此期间,您可以在gdb运行您的程序, gdb是Linux的调试器。 您还可以在启用所有mudflap检查并启用所有警告的情况下进行编译。 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: ========= .... 

您可以执行许多mudflap检查,上面使用默认选项运行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 ++。

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

通常,最好在您将用于运行代码的环境下进行开发。

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

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

您是否正在进行任何特定于平台的假设,例如数据类型的大小, 结构中的数据结构对齐或字节顺序 ?

一些提示:

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

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

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

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

  5. 在确定已经确定问题之前,不要责怪编译器(或操作系统或硬件)。 即使这样,也要怀疑自己的代码。


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

既然是家庭作业,我会说发布代码是一个更好的主意。 有什么比从专业程序员那里获得的更好的学习方法? 不过,我建议您在附近的评论中记下您实施的任何建议。

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

我推荐以下内容:

  1. 粘贴您的代码,因为这是一个非常有趣的问题
  2. 将此副本发送给cygwin开发人员
  3. 如果您需要生成更多在Linux下运行的程序,那么获得廉价的Linux VPS,它将使您的生活更轻松。

一旦你在Linux下工作(即支持你的VPS),尝试使用以下程序:

  • GDB
  • Valgrind的
  • strace的

此外,你可以尝试像电栅栏这样的库,以便在程序运行时捕获这些类似的东西。

最后,确保-Wall传递给gcc,你想要它传达的警告。