
我正在嘗試在我的 Ubuntu 和 Windows 機器上使用 GCC 和 VC9 編譯並執行下面的 C 程式。但是,我面臨以下問題:
在 Ubuntu 機器上:
GCC 編譯得很好,但是在運行時,我會看到以下提示:
Segmentation Fault (Core Dump).
在 Windows 機器上:
VC9 編譯並運作良好。 GCC 編譯正常,但程式執行時間進程終止。
這裡需要您的專家協助。這是我的程式碼:
#include <string.h>
#include <stdio.h>
int calc_slope(int input1,int input2)
{
int sum=0;
int start=input1;
int end=input2;
int curr=start;
//some validation:
if (input1>input2)
return -1;
while(curr<=end)
{
if (curr>100)
{
char *s="";
int length;
int left;
int right;
int cent;
sprintf(s,"%d",curr);
length=strlen(s);
s++;
do
{
//printf("curr=%d char=%c pointer=%d length=%d \n",curr,*s,s,length);
left = *(s-1) - '0';
cent = *s - '0';
right = *(s+1) - '0';
//printf("curr=%d l=%d c=%d r=%d\n",curr,left,cent,right);
if ( (cent>left && cent>right) || (cent<left && cent<right) )
{
sum+=1; //we have either a maxima or a minima.
}
s++;
} while (*(s+1)!='\0');
}
curr++;
}
return sum;
}
int main()
{
printf("%d",calc_slope(1,150));
return 0;
}
更新:
信用去往以利亞不僅幫助我追蹤錯誤,還向我介紹了gdb
它的回溯工具 ( bt
),該工具對於調試 gcc 編譯的程式非常有幫助。這是修改後的版本,我經過一番嘗試和錯誤後完成:
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
int calc_slope(int input1,int input2)
{
int sum=0;
int start=input1;
int end=input2;
int curr=start;
//some validation:
if (input1>input2)
return -1;
while(curr<=end)
{
if (curr>100)
{
int size=10;
char *s=(char*)malloc((size+1) * sizeof(char));
int left;
int right;
int cent;
sprintf(s,"%d",curr);
s++;
do
{
left = *(s-1) - '0';
cent = *s - '0';
right = *(s+1) - '0';
if ( (cent>left && cent>right) || (cent<left && cent<right) )
{
sum+=1; //we have either a maxima or a minima.
}
s++;
} while (*(s+1)!='\0');
}
curr++;
}
return sum;
}
int main()
{
printf("%d",calc_slope(1,150));
return 0;
}
答案1
A分段故障當程式嘗試存取為其分配的區域之外的記憶體時,就會發生這種情況。
在這種情況下,經驗豐富的 C 程式設計師可以看出問題發生在sprintf
呼叫的行中。但是,如果您無法判斷分段錯誤發生在哪裡,或者您不想費心閱讀程式碼來嘗試要弄清楚它,然後您可以使用調試符號(使用gcc
,-g
標誌執行此操作)構建程序,然後通過調試器運行它。
我複製了您的原始程式碼並將其貼到我命名為slope.c
.然後我像這樣構建它:
gcc -Wall -g -o slope slope.c
(這-Wall
是可選的。這只是為了讓它在更多情況下產生警告。這也可以幫助找出可能出現的問題。)
然後,我在調試器中運行該程序gdb
,首先運行gdb ./slope
以啟動gdb
該程序,然後在調試器中run
向調試器發出命令:
ek@Kip:~/source$ gdb ./slope
GNU gdb (GDB) 7.5-ubuntu
Copyright (C) 2012 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/ek/source/slope...done.
(gdb) run
Starting program: /home/ek/source/slope
warning: Cannot call inferior functions, you have broken Linux kernel i386 NX (non-executable pages) support!
Program received signal SIGSEGV, Segmentation fault.
0x001a64cc in _IO_default_xsputn () from /lib/i386-linux-gnu/libc.so.6
(不要擔心我的you have broken Linux kernel i386 NX
...support
訊息;它不會阻止gdb
有效地用於調試該程式。)
該資訊非常神秘......如果您沒有為 libc 安裝偵錯符號,那麼您將收到一條更神秘的訊息,其中包含十六進位位址而不是符號函數名稱_IO_default_xsputn
。幸運的是,這並不重要,因為我們真正想知道的是在你的程式中的哪個位置問題正在發生。
因此,解決方案是向後看,查看發生了哪些函數呼叫導致SIGSEGV
最終觸發訊號的系統庫中的特定函數呼叫。
gdb
(以及任何調試器)內建了此功能:它稱為堆疊追蹤或者回溯。我使用bt
調試器命令在以下位置生成回溯gdb
:
(gdb) bt
#0 0x001a64cc in _IO_default_xsputn () from /lib/i386-linux-gnu/libc.so.6
#1 0x00178e04 in vfprintf () from /lib/i386-linux-gnu/libc.so.6
#2 0x0019b234 in vsprintf () from /lib/i386-linux-gnu/libc.so.6
#3 0x0017ff7b in sprintf () from /lib/i386-linux-gnu/libc.so.6
#4 0x080484cc in calc_slope (input1=1, input2=150) at slope.c:26
#5 0x08048578 in main () at slope.c:52
(gdb)
您可以看到您的main
函數呼叫了該calc_slope
函數(您想要的),然後calc_slope
呼叫了sprintf
,這是(在這個系統上)透過呼叫其他幾個相關的函式庫函數來實現的。
您通常感興趣的是函數調用在你的程式中呼叫一個函數在你的程式之外。除非您使用的庫本身存在錯誤(在本例中libc
為庫文件提供的標準 C 庫libc.so.6
),否則導致崩潰的錯誤位於您的程式中,並且常將在最後一次通話時或附近在你的程式中。
在這種情況下,那就是:
#4 0x080484cc in calc_slope (input1=1, input2=150) at slope.c:26
這就是你的程式調用的地方sprintf
。我們知道這一點,因為sprintf
這是下一步。但即使沒有說明這一點,你也知道這一點,因為這就是第 26 行發生的情況,它說:
... at slope.c:26
在您的程式中,第 26 行包含:
sprintf(s,"%d",curr);
(您應該始終使用自動顯示行號的文字編輯器,至少對於您目前所在的行而言。這對於解釋編譯時錯誤以及使用偵錯器時顯示的執行時間問題非常有幫助。)
如中所討論的丹尼斯·卡爾斯梅克的回答,s
是一個單字節數組。 (不為零,因為您分配給它的值 ,""
是一個位元組長,也就是說,它等於, 與等於 的{ '\0' }
方式相同。)"Hello, world!\n"
{ 'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', '\n', '\0' }
所以為什麼可以這在某些平台上仍然有效(並且顯然在使用 Windows 的 VC9 編譯時也有效)?
人們經常說,當您分配記憶體然後嘗試存取其外部的記憶體時,就會產生錯誤。但事實並非如此。根據C和C++技術標準,這真正產生的是未定義的行為。
換句話說,什麼事情都有可能發生!
儘管如此,有些事情比其他事情更有可能發生。為什麼在某些實作上,堆疊上的小數組看起來像堆疊上的較大數組一樣工作?
這取決於堆疊分配的實作方式,而堆疊分配的實作方式因平台而異。您的可執行檔可能會為其堆疊分配比實際打算在任何時間使用的記憶體更多的記憶體。有時這可能允許您寫入尚未寫入的記憶體位置明確地在您的程式碼中提出了要求。當您在 VC9 中建置程式時,很可能會發生這種情況。
然而,即使在 VC9 中也不應該依賴這種行為。它可能依賴不同 Windows 系統上存在的不同版本的庫。但甚至更有可能問題是分配額外的堆疊空間是為了實際使用它,所以它實際上可能會被使用。然後,您會經歷「未定義行為」的噩夢,在這種情況下,多個變數最終可能儲存在同一個位置,其中寫入一個變數會覆蓋另一個變數......但並非總是如此,因為有時會寫入變數緩存在暫存器中,實際上並沒有立即執行(或者可以快取對變數的讀取,或者可以假設變數與之前相同,因為編譯器知道分配給它的記憶體尚未通過寫入變數本身)。
這讓我想到了為什麼該程式在使用 VC9 建置時可以工作的另一種可能的可能性。有可能,而且有一定的可能,一些數組或其他變量實際上是由您的程式分配的(可以包括由您的程式正在使用的庫分配的)以使用一位元組數組之後的空間s
。那麼,將s
其視為長度超過一個位元組的陣列將會產生存取該/那些變數/陣列的內容的效果,這也可能是不好的。
總之,當你犯下這樣的錯誤時,幸運的出現「分段錯誤」或「一般保護錯誤」等錯誤。當你不有了這個,你可能直到為時已晚才發現你的程式已經未定義的行為。
答案2
你好緩衝區溢位!
char *s="";
sprintf(s,"%d",curr);
length=strlen(s);
您為堆疊上的字串指派一個位元組,然後繼續向其中寫入多個位元組。最重要的是,您讀取的內容超出了該數組的末尾。請閱讀 C 手冊,尤其是有關字串和為其分配記憶體的部分。