首页 > 解决方案 > 尝试修复 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;
}

标签: c

解决方案


我一直在尝试使用和不使用调试器来解决这个问题!

调试器是诊断此类事情的重要工具,但您仍然必须批判性地思考您的代码在做什么,以便知道使用调试器查看什么。要记住的一个有用的想法是,每次崩溃都是由某些特定指令引起的*,您的工作是找到该指令,然后弄清楚您是如何到达那里的。

每次运行此程序时,我都会遇到此分段错误问题

好的,那么是什么指令导致了分段错误?您的调试器应该能够将您指向该行。我的说问题出在这里:

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))

这很有趣,因为eurovisionAddStatecountryNodeExists都在崩溃发生时的调用堆栈中,而您刚刚分配eurovision->Countries了 ,那么如果我们查看 会发生什么countryNodeExists?嗯,它做的第一件事就是调用崩溃的函数:

CountryNode cn = countryGetFirst(c);

它在这里传递的c正是eurovision->Countries调用者中的。嗯……继续。里面有什么countryGetFirst

while (cn->before)

嗯。为什么会崩溃?这里cnc在调用者中调用的,它eurovision->Countries在前一个调用者中,所以我们可以cn一直追溯到eurovision->Countriesfrom main。你觉得里面有什么?请记住,您是这样创建的:

(CountryNode)malloc(sizeof(struct CountryNode_t));

(旁白:你真的不应该投射 . 的结果malloc。在 SO 上搜索以找出原因。)

所以,你用malloc. 它的价值是什么?你实际上并不知道块中有什么,你只知道块的地址,而且你还没有在那个地址保存任何东西。所以,有两种可能性:要么为零,要么不为零。它非零的可能性非常好,那么接下来会发生什么?好吧,你执行循环体,所以你这样做:

cn = cn->before;

好的,所以beforethis 的成员中的任何CountryNode内容现在都在cn. 同样,我们不知道块中有什么,但可以很安全地假设这个before值不是指向另一个值的指针,CountryNode因为你还没有在那里保存任何东西。所以现在cn是一些随机值。你到达了循环的结尾,因为它是一个循环,所以你重复:

while (cn->before)

嗯...现在您正在取消引用 中的随机值cn,此时您会遇到段错误,因为不允许您从任何地方读取内存。

现在,我已经向您介绍了整个过程,以说明思考过程。但是你应该能够用你的调试器做同样的事情,即使你不马上理解问题......你总是可以在你知道问题之前的某个点设置一个断点,然后单步执行一次一个的说明,直到你遇到错误。您现在应该这样做,以帮助自己真正看到和理解问题。一旦你这样做,我相信你将能够弄清楚如何解决它。

祝你好运!

*归功于 Scott Knaster 在他的《如何编写 Macintosh 软件:Macintosh 调试参考》一书中的这一概念。


推荐阅读