c - 尝试修复 C 程序中的分段错误
问题描述
我是一名学生,我得到了这个我已经工作了一周的程序。每次运行这个程序时,我都会遇到这个分段错误问题,我改变并尝试了几乎所有东西,在互联网上阅读了很多,但没有任何帮助。我一直在尝试使用和不使用调试器来解决这个问题!
我认为这是我分配内存的方式eurovisionAddState()
,但我改变了很多次,没有任何效果......
这是我的程序:
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
#include <string.h>
#define NUM_OF_JUDGE_VOTES 10
#define NOT_FOUND -1
typedef enum eurovisionResult_t {
EUROVISION_NULL_ARGUMENT,
EUROVISION_OUT_OF_MEMORY,
EUROVISION_INVALID_ID,
EUROVISION_INVALID_NAME,
EUROVISION_STATE_ALREADY_EXIST,
EUROVISION_STATE_NOT_EXIST,
EUROVISION_JUDGE_ALREADY_EXIST,
EUROVISION_JUDGE_NOT_EXIST,
EUROVISION_SAME_STATE,
EUROVISION_SUCCESS
} EurovisionResult;
//Structs:
struct country_t {
int *ID;
char *CountryName;
char *SongName;
};
typedef struct country_t *Country;
//Nodes
typedef struct CountryNode_t {
Country data;
struct CountryNode_t *next;
struct CountryNode_t *before;
} *CountryNode;
typedef struct eurovision_t {
CountryNode Countries;
} *Eurovision;
//========
//Functions:
Eurovision eurovisionCreate() {
Eurovision euro = (Eurovision)malloc(sizeof(*euro));
euro->Countries = (CountryNode)malloc(sizeof(struct CountryNode_t));
return euro;
}
static Eurovision setupEurovision() {
Eurovision eurovision = eurovisionCreate();
assert(eurovision);
return eurovision;
}
CountryNode countryGetFirst(CountryNode cn) {
while (cn->before) {
cn = cn->before;
}
return cn;
}
bool countryNodeExists(CountryNode c, int ID) //Returns TRUE if country with the given ID exists,
{ // and FALSE if country with the given ID doesn't exist
CountryNode cn = countryGetFirst(c);
while (cn) {
if (*(cn->data->ID) == ID) {
return true;
}
cn = cn->next;
}
return false;
}
int countryNodeSize(CountryNode countryNode) //Returns the amount of countries in countryNode
{
CountryNode cn = countryGetFirst(countryNode);
int size = 0;
while (cn) {
size++;
cn = cn->next;
}
return size;
}
void countryNodePut(CountryNode countryNode,Country country) //Puts country inside the correct
{ //place (via ID comparison) in countryNode
//if country is first
if (countryNodeSize(countryNode) == 0) {
countryNode = (CountryNode)malloc(sizeof(struct CountryNode_t));
countryNode->before = NULL;
countryNode->next = NULL;
countryNode->data = country;
return;
}
CountryNode new_country_node = (CountryNode)malloc(sizeof(struct CountryNode_t));
new_country_node->data = country;
//If ID is before First
CountryNode first = countryGetFirst(countryNode);
if (*(first->data->ID) > *(country->ID)) {
new_country_node->next = first;
new_country_node->before = NULL;
first->before = new_country_node;
return;
}
//check if the country exists, and replace the data
if (countryNodeExists(countryNode, *(country->ID))) {
CountryNode cn = countryGetFirst(countryNode);
while (cn) {
if (*(cn->data->ID) == *(country->ID)) {
cn->data = country;
return;
}
cn = cn->next;
}
}
//place it in its place
CountryNode cn = countryGetFirst(countryNode);
while (cn->next) { //cn->next so we wouldnt try to read from a null
if (*(cn->data->ID) < *(country->ID) && *(cn->next->data->ID) > *(country->ID)) {
cn->next->before = new_country_node;
new_country_node->before = cn;
new_country_node->next = cn->next;
cn->next = new_country_node;
return;
}
}
//got here if countryNode should be last
cn->next = new_country_node;
new_country_node->before = cn;
}
bool checkInvalidName(const char *name) {
int i = 0;
while (*(name + i) != '\0') {
if ((*(name + i) < 'a' || *(name + i) > 'z') && *(name + i) != ' ')
return true;
i++;
}
return false;
}
EurovisionResult eurovisionAddState(Eurovision eurovision, int stateId,
const char *stateName,
const char *songName)
{ //CHECK IF stateName IS VALID
if (checkInvalidName(stateName))
return EUROVISION_INVALID_NAME;
//----
//CHECK IF stateId IS POSITIVE
if (stateId < 0)
return EUROVISION_INVALID_ID;
//----
//CHECK IF THE SAME STATE EXIST
if (countryNodeExists(eurovision->Countries, stateId))
return EUROVISION_STATE_ALREADY_EXIST;
//----
Country state = (Country)malloc(sizeof(struct country_t));
if (!state) {
return EUROVISION_OUT_OF_MEMORY;
}
state->ID = (int *)malloc(sizeof(int));
*(state->ID) = stateId;
state->CountryName = (char*)malloc(sizeof(char) * strlen(stateName) + 1);
strcpy(state->CountryName, stateName);
state->SongName = (char *)malloc(sizeof(char) * strlen(songName) + 1);
strcpy(state->SongName, songName);
countryNodePut(eurovision->Countries, state);
/* //TEST - DELETE THIS
Country stateNew = countryNodeGet(eurovision->Countries, stateId);
printf("eurovisionAddState: after mapPut-Cname=%s,Sname=%s,id=%d\n",(stateNew->CountryName), (stateNew->SongName), *(stateNew->ID));
//----*/
return EUROVISION_SUCCESS;
}
int main() {
printf("Starting Test\n");
Eurovision eurovision = setupEurovision();
printf("After setupEurovision()\n");
eurovisionAddState(eurovision, 1, "malta", "chameleon");
printf("After eurovisionAddState of Malta\n");
eurovisionAddState(eurovision, 2, "croatia", "the dream");
eurovisionAddState(eurovision, 3, "russia", "scream");
eurovisionAddState(eurovision, 4, "moldova", "stay");
eurovisionAddState(eurovision, 5, "cyprus", "replay");
eurovisionAddState(eurovision, 6, "spain", "la venda");
eurovisionAddState(eurovision, 7, "italy", "soldi");
eurovisionAddState(eurovision, 8, "france", "roi");
eurovisionAddState(eurovision, 9, "germany", "sister");
eurovisionAddState(eurovision, 10, "united kingdom", "bigger than us");
eurovisionAddState(eurovision, 11, "armenia", "walking out");
eurovisionAddState(eurovision, 12, "austria", "limits");
eurovisionAddState(eurovision, 13, "ireland", "twenty two");
eurovisionAddState(eurovision, 14, "netherlands", "arcade");
eurovisionAddState(eurovision, 15, "sweden", "too late for love");
return 0;
}
解决方案
我一直在尝试使用和不使用调试器来解决这个问题!
调试器是诊断此类事情的重要工具,但您仍然必须批判性地思考您的代码在做什么,以便知道使用调试器查看什么。要记住的一个有用的想法是,每次崩溃都是由某些特定指令引起的*,您的工作是找到该指令,然后弄清楚您是如何到达那里的。
每次运行此程序时,我都会遇到此分段错误问题
好的,那么是什么指令导致了分段错误?您的调试器应该能够将您指向该行。我的说问题出在这里:
while (cn->before)
它还告诉我我们是如何达到这一点的以及问题是什么:
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
* frame #0: 0x000000010000064c DeleteThis`countryGetFirst(cn=0x8b480850ff078b48) at main.c:65
frame #1: 0x0000000100000688 DeleteThis`countryNodeExists(c=0x0000000102905f60, ID=1) at main.c:74
frame #2: 0x0000000100000a1e DeleteThis`eurovisionAddState(eurovision=0x0000000102908750, stateId=1, stateName="malta", songName="chameleon") at main.c:182
frame #3: 0x0000000100000b76 DeleteThis`main at main.c:212
frame #4: 0x00007fff617253d5 libdyld.dylib`start + 1
frame #5: 0x00007fff617253d5 libdyld.dylib`start + 1
(“DeleteThis”只是我给这个项目起的名字——它提醒我在接下来的 20 分钟后我不需要保存它。另外,我删除了你的一些额外的空白行,所以我的行号可能赢了不符合你的。)
这里重要的是我们正在查看堆栈,我们可以看到它countryGetFirst
是由 调用的countryNodeExists
,而后者又是由 调用的eurovisionAddState
,它是从调用的main
。好的,接下来我们需要知道为什么有问题的线路崩溃了。分段错误通常意味着您试图取消引用错误的指针。确实, crash 的行确实取消了对某些指针的引用,但它很糟糕吗?让我们弄清楚它是从哪里来的……
您的main
函数首先设置一个eurovision
像这样的变量:
Eurovision eurovision = setupEurovision();
那么做setupEurovision
什么呢?它只是为 Eurovision 结构分配一些空间,然后分配另一个块并eurovision->Countries
指向它:
Eurovision euro=(Eurovision)malloc(sizeof(*euro));
euro->Countries=(CountryNode)malloc(sizeof(struct CountryNode_t));
return euro;
到目前为止看起来还不错,对吧?接下来,那东西会发生什么?好吧,接下来你添加一个国家:
eurovisionAddState(eurovision, 1, "malta", "chameleon");
为此,请eurovisionAddState
致电:
if(countryNodeExists(eurovision->Countries,stateId))
这很有趣,因为eurovisionAddState
和countryNodeExists
都在崩溃发生时的调用堆栈中,而您刚刚分配eurovision->Countries
了 ,那么如果我们查看 会发生什么countryNodeExists
?嗯,它做的第一件事就是调用崩溃的函数:
CountryNode cn = countryGetFirst(c);
它在这里传递的c
正是eurovision->Countries
调用者中的。嗯……继续。里面有什么countryGetFirst
?
while (cn->before)
嗯。为什么会崩溃?这里cn
是c
在调用者中调用的,它eurovision->Countries
在前一个调用者中,所以我们可以cn
一直追溯到eurovision->Countries
from main
。你觉得里面有什么?请记住,您是这样创建的:
(CountryNode)malloc(sizeof(struct CountryNode_t));
(旁白:你真的不应该投射 . 的结果malloc
。在 SO 上搜索以找出原因。)
所以,你用malloc
. 它的价值是什么?你实际上并不知道块中有什么,你只知道块的地址,而且你还没有在那个地址保存任何东西。所以,有两种可能性:要么为零,要么不为零。它非零的可能性非常好,那么接下来会发生什么?好吧,你执行循环体,所以你这样做:
cn = cn->before;
好的,所以before
this 的成员中的任何CountryNode
内容现在都在cn
. 同样,我们不知道块中有什么,但可以很安全地假设这个before
值不是指向另一个值的指针,CountryNode
因为你还没有在那里保存任何东西。所以现在cn
是一些随机值。你到达了循环的结尾,因为它是一个循环,所以你重复:
while (cn->before)
嗯...现在您正在取消引用 中的随机值cn
,此时您会遇到段错误,因为不允许您从任何地方读取内存。
现在,我已经向您介绍了整个过程,以说明思考过程。但是你应该能够用你的调试器做同样的事情,即使你不马上理解问题......你总是可以在你知道问题之前的某个点设置一个断点,然后单步执行一次一个的说明,直到你遇到错误。您现在应该这样做,以帮助自己真正看到和理解问题。一旦你这样做,我相信你将能够弄清楚如何解决它。
祝你好运!
*归功于 Scott Knaster 在他的《如何编写 Macintosh 软件:Macintosh 调试参考》一书中的这一概念。
推荐阅读
- java - TradingView Widget 未在 Android 6.0 上加载
- r - 可以使用sqldf将数据库中已经存在的表的数据导入R中的data.frame吗?
- excel - 如何搜索子字符串,并返回其右侧 10 个字符的所有值
- bigcommerce - 在模板条件语句中显示产品价格时遇到问题
- c - string.h 函数 - strncpy 和 strncat 奇怪的行为
- mongodb - 如何使用对象数组中的一个查询更新 mongodb 中的多个文档
- python - 停用词删除和词形还原后的空白
- r - R:为什么第一个代码运行,但第二个代码导致错误?
- c# - 递归方法一直引用同一个局部变量
- regex - 正则表达式在点之后得到一切