对于我的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;
}
答案 0 :(得分:14)
我并不是说听起来粗鲁,但可能你的代码很糟糕,而不是编译器。 ;)像这样的问题实际上比你想象的更常见,因为不同的操作系统和编译器将有不同的方式在堆栈和堆中组织应用程序的数据。前者可能特别成问题,特别是如果您最终覆盖堆栈上的内存,或引用系统已决定用于其他内容的释放内存。所以基本上,你有时会侥幸逃脱,但有时你的应用会窒息而死。无论哪种方式,如果它是段错误的,那是因为你试图引用你不被允许的内存,所以它更像是一个“快乐的巧合”,它没有在另一个系统/编译器下崩溃。
但实际上,段错误是一个段错误,所以你应该调试你的代码来寻找内存损坏,而不是调整编译器的配置来找出出错的地方。
编辑:好的,我明白你的意思了......我以为你是在解决这个问题,“X很糟糕,但Y工作得很好!”态度,但似乎是你的TA有这个。 ;)
无论如何,这里有一些调试问题的更多提示:
答案 1 :(得分:8)
就像其他人所说的那样,你可能想在这里发布一些代码,即使这不是你问题的真正意义。让每个人在这里浏览你的代码,看看他们是否能找到导致段错误的原因,这可能仍然是一个很好的学习经历。
但是,问题是,有很多平台依赖的,以及基本上随机的因素影响C程序。虚拟内存意味着有时,访问未分配的内存似乎可以正常工作,因为您点击了在某个早期点分配的页面的未使用部分。其他时候,它会发生段错误,因为您点击了一个尚未分配给您的进程的页面。这真的无法预测。这取决于你的内存分配位置,它是在页面的边缘,还是在中间?这取决于操作系统和内存管理器,到目前为止已经分配了哪些页面,......你明白了。系统上安装了不同的编译器,相同编译器的不同版本,不同的操作系统,不同的软件,驱动程序或硬件,当您访问未分配的内存时,无论您是否收到段错误,都会发生任何变化。
至于TA声称cygwin更“松懈”,这是垃圾,原因很简单。 两个编译器都没有发现错误!如果“本机”GCC编译器真的不那么松懈,它会在编译时给你一个错误。 Segfaults不是由编译器生成的。编译器无法确保您获得段错误而不是看似有效的程序。
答案 2 :(得分:4)
我没有听说过Cygwin下GCC奇怪的任何具体内容,但在你的情况下,使用-Wall命令行选项gcc来显示所有警告可能是一个好主意,看它是否找到任何警告可能会导致代码中的段错误。
答案 3 :(得分:3)
你的代码中肯定有一个错误。 Windows内存管理器可能比Linux内存管理器更宽松。在Windows上,你可能会使用内存做坏事(比如覆盖数组边界,内存泄漏,双重释放等等),但它可以让你逃脱它。可以在 http://www.joelonsoftware.com/articles/APIWar.html找到与此相关的着名故事(在那篇(有点冗长的)文章中搜索“模拟城市”)。
答案 4 :(得分:2)
Cygwin的gcc版本可能有其他默认标志和调整设置(例如wchar_t为2个字节),但我怀疑它对代码更加“松懈”,即便如此 - 你的代码不应该崩溃。如果确实如此,那么很可能您的代码中存在需要修复的错误。例如,您的代码可能依赖于特定大小的wchar_t,或者可能执行根本无法保证工作的代码,例如写入字符串文字。
如果你编写干净的代码,那么它也会在linux上运行。我目前正在运行firefox和KDE桌面,它们由数百万条C ++行组成,我没有看到这些应用程序崩溃:)
我建议您将代码粘贴到您的问题中,以便我们查看出现了什么问题。
与此同时,您可以在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 ++。
答案 5 :(得分:2)
几乎可以肯定是指针错误或缓冲区溢出,可能是未初始化的变量。
未初始化的指针通常指向任何东西,但有时会指向某个东西;从中读取或写入它通常会使程序崩溃,但话又说不了。
从释放的记忆中写作或阅读是同一个故事;你可能会逃避它,然后再也许不会。
这些情况完全取决于堆栈,堆的布局以及运行时的运行方式。很有可能制作一个在一个编译器/运行时组合而不是另一个编译器/运行时组合上运行的坏程序,只是因为在一个上它覆盖了无关紧要的东西(那么多),或者一个未初始化的变量“发生”包含一个有效的它所使用的上下文的值。
答案 6 :(得分:1)
GCC的版本可能不是问题。它更可能是运行时库中的一个区别,以及代码中的一个错误,它在运行Windows版本的运行时不会显示出来。如果您想要更具体的答案,您可能希望发布段错误的代码和更多背景信息。
通常,最好在您将用于运行代码的环境下进行开发。
答案 7 :(得分:1)
您是否正在制定任何特定于平台的假设,例如struct
中的data types,data structure alignment或endianness的大小?
答案 8 :(得分:1)
分段错误意味着您尝试访问无法访问的内存,这通常意味着您尝试取消引用空指针或双重删除内存或获得野指针。有两个原因可能是你在cygwin上看起来没问题,而不是在Linux上:或者是内存管理员的问题,或者你在其中一个上比其他人更幸运。您的代码几乎肯定是错误的。
要解决此问题,请查看指针的使用情况。考虑用智能指针代替原始指针。考虑在之后立即搜索delete并将指针归零(尝试删除空指针是安全的)。如果您可以在Linux上获得破解,请尝试通过gdb获取堆栈跟踪,并查看它发生的行是否有任何明显错误。检查如何使用未初始化的所有指针。如果您可以访问内存调试工具,请使用它。
答案 9 :(得分:1)
一些提示:
发布您的代码。我敢打赌,你会得到一些好的输入,让你成为更好的程序员。
使用-wall
选项启用警告并更正报告的所有问题。同样,它可以帮助您成为更好的程序员。
使用调试器逐步执行代码。除了帮助您了解问题所在之外,它还有助于您成为更好的程序员。
继续使用Subversion或其他源代码控制系统。
在确定您已确定问题之前,不要责怪编译器(或操作系统或硬件)。即使这样,也要怀疑自己的代码。
Linux上的GCC源代码与Cygwin上的GCC相同。由于Cygwin POSIX仿真层和底层Windows API,存在平台之间的差异。额外的层可能比底层硬件更宽容,但这不是可靠的。
既然是家庭作业,我会说发布代码是一个更好的主意。有什么比从专业程序员那里获得的更好的学习方法?不过,我建议您在附近的评论中记下您实施的任何建议。
答案 10 :(得分:1)
分段错误是在不存在(或先前释放的)地址处访问内存的结果。我发现非常有趣的是代码没有在cygwin下进行段错误。这可能意味着你的程序使用了一个指向其他进程的地址空间的狂野指针,实际上能够读取它(喘气),或者(更有可能)直到程序在Linux下运行才导致实际导致段错误的代码
我建议如下:
一旦您在Linux下工作(即支持您的VPS),请尝试使用以下程序:
此外,您可以尝试使用电栅栏这样的库来捕捉程序运行时发生的这类事情。
最后,确保-Wall传递给gcc,你想要传达的警告。