发生异常,在 c 中使用 free() 时出现分段错误;

Exception occurred, segmentation fault while using free() in c;

提问人:Nishu Patel 提问时间:9/6/2023 最后编辑:chqrlieNishu Patel 更新时间:9/9/2023 访问量:87

问:

我的代码以某种方式运行,但它没有给出预期的结果,所以为了理解它,我开始调试,它显示发生了异常,我使用的行出现了分段错误。 这是我的代码:free()

struct stack
{
    char ch;
    struct stack *prev;
} *top = NULL;

typedef struct stack st;

char pop();
void push(char c);
int evaluate(char arr[100]);

int main()
{
    char arr[100];
    printf("Enter the string to check format of string in wcwR");
    gets(arr);
    if (evaluate(arr) == 1)
    {
        printf("Entered string is in correct format");
    }
    else
    {
        printf("Entered string is in wrong format");
    }
}

int evaluate(char arr[100])
{
    int i;
    for (i = 0; arr[i] != 'c' && arr[i] != 'C'; i++)
    {
        push(arr[i]);
    }
    i++;
    int j = i;
    int flag = 0;
    while (arr[j] != '\0')
    {
        if (arr[j] != pop())
        {
            return 0;
        }
        else
        {
            flag = 1;
        }
    }
    if (flag == 1)
    {
        return 1;
    }
}

void push(char c)
{
    st *p;
    p = (struct stack *)malloc(sizeof(struct stack));
    p->ch = c;
    p->prev = top;
    top = p;
}

char pop()
{
    st *temp;
    char item;
    temp = top;
    item = top->ch;
    top = top->prev;
    **free(temp);** //here exception occurred 
    return item;
}

请帮我解决这个问题,任何人都可以建议任何合适的技术来自己解决这样的问题吗?谢谢。

c 异常 数据结构 malloc free

评论

4赞 Barmar 9/6/2023
停止使用 gets()
2赞 Barmar 9/6/2023
第一个循环在到达字符串末尾时不会停止。for
2赞 Barmar 9/6/2023
不要投射 malloc
2赞 Barmar 9/6/2023
pop()不检查堆栈是否为空。
0赞 Ted Lyngmo 9/6/2023
如果出现以下情况,则该函数不会返回承诺的 。evaluateintflag != 1

答:

1赞 Mark Adler 9/6/2023 #1

您的异常不会发生在您认为发生的地方。弹出时您不会检查空堆栈,即 .然后,您正在尝试使用 取消引用。top == NULLNULLtop->ch

代码还存在其他问题。代替 使用,后者已弃用。末尾没有返回值 if(如果“c”或“C”后面没有字符,则可以返回)。只是没有.您的循环可以从字符串的末尾运行,因为您没有检查 .您没有检查 的返回值。fgets()gets()flag == 0return flag;iffor\0malloc()

3赞 arfneto 9/6/2023 #2

我将向你展示一种解决此类问题的通用方法。我什至不会试图说它是一个好方法,但它总体上对我有用,并且在第一次运行中就在这里工作,至少在我运行的几次测试中是这样。

(这是一篇很长的文章,因为我正在使用从代码中复制和粘贴以及注释来构建示例)。

关于发布的代码

我不清楚你想做什么。一般来说,最好在代码之前发布任务的描述。阅读代码,我相信:

  • 你的目标是函数evaluate()
  • 这个想法是检查格式 wCwR,一个像“1234C4321”这样的字符串
    • 中心有一个 ORCc
    • 如果 前面的字符串在 之后反转,则返回CCevaluatetrue
  • A 的使用是根据要求。stack

笔记:

  • 不要使用固定值,如 .封装事物和使用 jus 指针更容易arr[100]
  • 不要编写交互式代码。它太慢了,太不方便了。使用文件、常量、工厂函数。
  • 永远不要使用.几十年来,它已被弃用gets
  • 返回0false
  • 永远不要使用全局性的东西
  • 将代码写入(并测试它)就像将代码写入内存一样。freemalloc
  • main应该是代码的第一个函数,如果可能的话,在单独的文件中。

在哪里?stack

这是来自您的代码:struct

struct stack
{
  char ch;
  struct stack *prev;
} *top = NULL;

这是有问题的,也是脆弱的。

  • 这是一个 .不是.在对堆栈、列表、树或类似容器等内容进行编程时,更安全的做法是注意容器是节点的(可能是空的)集合。每个节点都包含(或指向)一些数据。Nodestack
  • 堆栈不是节点。节点不是堆栈。
  • 代码中的这个东西是全局的。一般解决不了任何问题。整个代码中只能有一个。如果您需要一对堆栈怎么办?堆栈阵列?一堆结构?

一个可能的Stack


typedef struct st_node
{
  char            ch;
  struct st_node* prev;
} Node;

typedef struct
{
  unsigned size;
  Node*    top;
} Stack;

在这里,我们有一个节点堆栈。以及堆栈本身中的一些元数据:大小。

我将进一步将其介绍为一个完整的示例,其中包含 C 代码和输出。我正在从程序中复制和粘贴。

堆栈不是任务

我们需要一个堆栈实现。但是我们绝不想将程序和堆栈代码混合在一起,因为

  • 我们可以用语言或从其他人或图书馆获得它
  • 如果我们真的需要为堆栈编写代码,我们希望将其分开,以便能够在其他程序中使用它

堆栈的函数

我们需要最低限度的 POP、PUSH 和 TOP 操作,以及构造函数和析构函数。让我们考虑一下:

Stack* create();
Stack* destroy(Stack*);

char pop(Stack* stack);
int  push(char c, Stack* stack);
char top(Stack* stack);

使用指针使得在代码中写入 1 或 1,000 个堆栈变得微不足道。就像在 RAII 概念中一样,我们将首先编写 和 .C++createdestroy

和 的可能代码createdestroy

Stack* create()
{
    Stack* stack = (Stack*)malloc(sizeof(Stack));
    if (stack == NULL) return NULL;
    stack->size = 0;  // empty
    stack->top  = NULL;
    return stack;
}

没什么好看的:我们分配一个堆栈,并通过使 .size = 0

(题外话:请记住,C 语言中有一个邪教,说不要在 中使用强制转换。我会的,永远。作为对程序员和读者的提醒。即使是像这样非常简单的代码,也会为OR分配内存,如你所见。编译器不需要这种强制转换,但通过编写和重复它,您可以确定什么是什么,并防止许多错误并为您省去很多麻烦。那种耗费金钱、工作和时间的麻烦。malloc()NodeStack

(这个邪教是基于90年代一本从未更新的书和一些甚至更早的Usenet帖子。在网络编程或文本编辑等领域,通常在几行代码中为多种类型的缓冲区分配内存,甚至表达式也是函数调用,用于评估我们需要的缓冲区的大小。这样一来,演员就像向我们清楚地说明什么是什么。编译器不需要强制转换,但也没有薪水)。malloc()

Stack* destroy(Stack* stack)
{
    if (stack == NULL) return NULL;  // no stack
    while (stack->size > 0)
    {
        Node* prev = stack->top->prev;  // save
        free(stack->top);               // destroy top
        stack->top = prev;              // reassign
        stack->size -= 1;
    }
    free(stack); // now the stack 
    return NULL;
}

只是预期的,但每个堆栈都有自己的事实使事情变得更简单。导航堆栈,释放节点,然后导航堆栈。为什么总是回来?这只是为了使在同一行中重置指针成为可能。每天都有数以千计的程序因使用无效指针而崩溃。我们对此没有余地。sizefreeNULL

这个程序已经可以运行了:

int main(void)
{
    Stack* one = create();
    one   = destroy(one);  // destroy stack, clear pointer
    return 0;
}

只是为了道义上的支持。

top

TOP非常简单:只需返回堆栈顶部的值,保持堆栈不变:一行


char top(Stack* stack)
{  // return char in the top, or negative value
    if (stack == NULL) return -1;
    if (stack->size == 0) return -2;
    return stack->top->ch;
}

pop

POP 删除顶部的元素并将其返回给调用方:

char pop(Stack* stack)
{
    if (stack == NULL) return -1;
    if (stack->size == 0) return -1;
    char  top  = stack->top->ch;    /// save it
    Node* temp = stack->top;        // save the pointer
    stack->top = stack->top->prev;  // go back
    stack->size -= 1;               // 1 less
    free(temp);
    return top;
}

正如预期的那样,保存元素,重新分配顶部,节点,调整大小,只需几行。free

push

int push(char c, Stack* stack)
{
    if (stack == NULL) return -1;
    Node* nw = (Node*)malloc(sizeof(Node));
    if (nw == NULL) return -1;
    nw->ch     = c;
    nw->prev   = stack->top;
    stack->top = nw;
    stack->size += 1;
    return 0;
}

PUSH 是创建一个节点并将其链接在堆栈顶部的问题。

show:辅助函数

通常,堆栈没有显示其内容的方法,除了测试。但可以肯定的是,我们正在测试,所以:

int show(Stack* stack, const char* msg)
{
    if (stack == NULL) return -1;
    if (msg != NULL) printf("%s", msg);
    if (stack->size == 0)
    {
        printf("    stack is empty\n");
        return 0;
    }
    printf("    %d elements in stack:\n\n", stack->size);
    Node* p = stack->top;
    for (unsigned i = 0; i < stack->size; i += 1)
    {
        printf("%8d,'%c' [%3d]\n", 1+i, p->ch, (int)p->ch);
        p = p->prev;
    }
    printf("-----\n");
    return 0;
}

这个版本有一个可选的标题,在测试中非常方便。请参阅示例。

“str_to_stack”

int str_to_stack(const char* str, Stack* stack)
{
    if (stack == NULL) return -1;
    if (str == NULL) return -2;
    const char* p = str;
    while (*p != 0)
    {
        push(*p, stack);
        p++;
    };
    return 0;
}

这只是将字符串推送到堆栈中的一种方式。在一行中。因此,我们可以将代码编写为:

    Stack* one = create();

    str_to_stack("Stack", one);
    show(one, "\"Stack\" added =>");

    str_to_stack(" Overflow", one);
    show(one, "\" Overflow\" added =>");

只是为了看看

"Stack" added =>    5 elements in stack:

       1,'k' [107]
       2,'c' [ 99]
       3,'a' [ 97]
       4,'t' [116]
       5,'S' [ 83]
-----
" Overflow" added =>    14 elements in stack:

       1,'w' [119]
       2,'o' [111]
       3,'l' [108]
       4,'f' [102]
       5,'r' [114]
       6,'e' [101]
       7,'v' [118]
       8,'O' [ 79]
       9,' ' [ 32]
      10,'k' [107]
      11,'c' [ 99]
      12,'a' [ 97]
      13,'t' [116]
      14,'S' [ 83]
-----

这比循环或交互式代码要好得多......

堆栈文件

stuff.h堆栈头文件

#include <stdio.h>
#include <stdlib.h>

typedef struct st_node
{
    char            ch;
    struct st_node* prev;
} Node;

typedef struct
{
    unsigned size;
    Node*    top;
} Stack;

Stack* create();
Stack* destroy(Stack*);

char pop(Stack* stack);
int  push(char c, Stack* stack);
// helper function to show stack contents
int show(Stack* stack, const char* msg);
// helper function to put a string in a stack
int  str_to_stack(const char* str, Stack* stack);
char top(Stack* stack);

stuff.c堆栈实现

#include "stuff.h"

Stack* create()
{
    Stack* stack = (Stack*)malloc(sizeof(Stack));
    if (stack == NULL) return NULL;
    stack->size = 0;  // empty
    stack->top  = NULL;
    return stack;
}

Stack* destroy(Stack* stack)
{
    if (stack == NULL) return NULL;  // no stack
    while (stack->size > 0)
    {
        Node* prev = stack->top->prev;  // save
        free(stack->top);               // destroy top
        stack->top = prev;              // reassign
        stack->size -= 1;
    }
    free(stack);  // now the stack 
    return NULL;
}

int push(char c, Stack* stack)
{
    if (stack == NULL) return -1;
    Node* nw = (Node*)malloc(sizeof(Node));
    if (nw == NULL) return -1;
    nw->ch     = c;
    nw->prev   = stack->top;
    stack->top = nw;
    stack->size += 1;
    return 0;
}

char pop(Stack* stack)
{
    if (stack == NULL) return -1;
    if (stack->size == 0) return -1;
    char  top  = stack->top->ch;    /// save it
    Node* temp = stack->top;        // save the pointer
    stack->top = stack->top->prev;  // go back
    stack->size -= 1;               // 1 less
    free(temp);
    return top;
}

int show(Stack* stack, const char* msg)
{
    if (stack == NULL) return -1;
    if (msg != NULL) printf("%s", msg);
    if (stack->size == 0)
    {
        printf("    stack is empty\n");
        return 0;
    }
    printf("    %d elements in stack:\n\n", stack->size);
    Node* p = stack->top;
    for (unsigned i = 0; i < stack->size; i += 1)
    {
        printf("%8d,'%c' [%3d]\n", 1+i, p->ch, (int)p->ch);
        p = p->prev;
    }
    printf("-----\n");
    return 0;
}

int str_to_stack(const char* str, Stack* stack)
{
    if (stack == NULL) return -1;
    if (str == NULL) return -2;
    const char* p = str;
    while (*p != 0)
    {
        push(*p, stack);
        p++;
    };
    return 0;
}

char top(Stack* stack)
{  // return char in the top, or negative value
    if (stack == NULL) return -1;
    if (stack->size == 0) return -2;
    return stack->top->ch;
}

因此,我们可以将标头包含在任何程序中,然后进行编译stuff.c

完整的测试

int main(void)
{
    Stack* one = create();

    str_to_stack("Stack", one);
    show(one, "\"Stack\" added =>");

    str_to_stack(" Overflow", one);
    show(one, "\" Overflow\" added =>");

    printf("TOP is '%c'\n", top(one));
    show(one,"stack now: ");

    // copy to another stack
    Stack* other = create();
    printf("Reversing a stack into another\n");
    while (one->size > 0) push(pop(one), other);

    show(other, "new stack (reversed): ");
    show(one, "original (should be now empty): ");
    one = destroy(one);  // destroy stack, clear pointer
    other = destroy(other);  
    return 0;
}

这个程序

  • 创建堆栈one
  • 推送字符串“Stack”
  • 显示堆栈内容
  • 推送字符串 Overflow
  • 显示堆栈的顶部
  • 创建 ,第二个堆栈other
  • 复制到oneother
  • 列出两个堆栈
  • 销毁两个堆栈

输出

"Stack" added =>    5 elements in stack:

     1,'k' [107]
     2,'c' [ 99]
     3,'a' [ 97]
     4,'t' [116]
     5,'S' [ 83]
-----
" Overflow" added =>    14 elements in stack:

     1,'w' [119]
     2,'o' [111]
     3,'l' [108]
     4,'f' [102]
     5,'r' [114]
     6,'e' [101]
     7,'v' [118]
     8,'O' [ 79]
     9,' ' [ 32]
    10,'k' [107]
    11,'c' [ 99]
    12,'a' [ 97]
    13,'t' [116]
    14,'S' [ 83]
-----
TOP is 'w'
stack now:     14 elements in stack:

     1,'w' [119]
     2,'o' [111]
     3,'l' [108]
     4,'f' [102]
     5,'r' [114]
     6,'e' [101]
     7,'v' [118]
     8,'O' [ 79]
     9,' ' [ 32]
    10,'k' [107]
    11,'c' [ 99]
    12,'a' [ 97]
    13,'t' [116]
    14,'S' [ 83]
-----
Reversing a stack into another
new stack (reversed):     14 elements in stack:

     1,'S' [ 83]
     2,'t' [116]
     3,'a' [ 97]
     4,'c' [ 99]
     5,'k' [107]
     6,' ' [ 32]
     7,'O' [ 79]
     8,'v' [118]
     9,'e' [101]
    10,'r' [114]
    11,'f' [102]
    12,'l' [108]
    13,'o' [111]
    14,'w' [119]
-----
original (should be now empty):     stack is empty

怎么样evaluate()

现在我们有一个可以使用的堆栈。让我们改成evaluate

    int is_wcwr(const char* str)

返回 if 的函数是预期格式的字符串。1str

可能的实现

int is_wcwr(const char* str)
{
    if (str == NULL) return 0;
    // size must be odd
    size_t size = strlen(str);
    if (size % 2 == 0) return 0; // false
    // must have 'c' or 'C' in the center
    size_t pivot = size / 2;
    if ((str[pivot] != 'c') && (str[pivot] != 'C'))
        return 0;
    Stack* stack = create();
    // put 2nd half into stack
    str_to_stack((const char*) str+pivot+1, stack);
    // now compares 1st half of string with stack contents
    for (size_t i = 0; i < pivot; i+=1)
    {   // pair is ch_l ch_r
        char ch_l = *(str + i);
        char ch_r = pop(stack);
        if (ch_l != ch_r)
        {
            stack = destroy(stack);
            return 0;
        }
    }
    stack = destroy(stack);
    return 1;
}

逻辑很简单,实际上不需要堆栈。但我们肯定可以使用一个。 不变量:

  • 字符串的长度必须为奇数
  • 在中心,我们有或cC

所以

  • 我们创建一个堆栈,并将字符串的后半部分推入堆栈。
  • 将堆栈中的值与字符串中的值进行比较,只要它们相等即可
  • 发现差异后立即返回

测试程序

int main(void)
{
  const char* res[]    = {"is not", "is"};
  const char* test[] = {
      "123456C654321",
      "23456C654321",
      "",
      "test",
      "tests",
      "tects",
      "teCts",
      "xyzCzyx"
  };

  const unsigned N = sizeof(test) / sizeof(test[0]);

  printf("Total of %d strings to test\n", N);
  for (unsigned i = 0; i < N; i += 1)
      printf(
          "\t\"%s\" %s in the wcwR format\n",
          test[i], res[is_wcwr(test[i])]);
  return 0;
}

此代码允许在顶部输入测试字符串并运行代码。单个循环。没有读取,没有文件。

输出

Total of 8 strings to test
        "123456C654321" is in the wcwR format
        "23456C654321" is not in the wcwR format
        "" is not in the wcwR format
        "test" is not in the wcwR format
        "tests" is not in the wcwR format
        "tects" is not in the wcwR format
        "teCts" is not in the wcwR format
        "xyzCzyx" is in the wcwR format

完整代码

main.c

#include <string.h>
#include "stuff.h"

int is_wcwr(const char*);

int main(void)
{
    const char* res[]    = {"is not", "is"};
    const char* test[] = {
        "123456C654321",
        "23456C654321",
        "",
        "test",
        "tests",
        "tects",
        "teCts",
        "xyzCzyx"
    };
    const unsigned N = sizeof(test) / sizeof(test[0]);

    printf("Total of %d strings to test\n", N);
    for (unsigned i = 0; i < N; i += 1)
        printf(
            "\t\"%s\" %s in the wcwR format\n",
            test[i], res[is_wcwr(test[i])]);
    return 0;
}

int is_wcwr(const char* str)
{
    if (str == NULL) return 0;
    // size must be odd
    size_t size = strlen(str);
    if (size % 2 == 0) return 0; // false
    // must have 'c' or 'C' in the center
    size_t pivot = size / 2;
    if ((str[pivot] != 'c') && (str[pivot] != 'C'))
        return 0;
    Stack* stack = create();
    // put 2nd half into stack
    str_to_stack((const char*) str+pivot+1, stack);
    // now compares 1st half of string with stack contents
    for (size_t i = 0; i < pivot; i+=1)
    {   // pair is ch_l ch_r
        char ch_l = *(str + i);
        char ch_r = pop(stack);
        if (ch_l != ch_r)
        {
            stack = destroy(stack);
            return 0;
        }
    }
    stack = destroy(stack);
    return 1;
}

评论

0赞 Nishu Patel 9/6/2023
实际上我在上大学(sem3),我们的教授是这样教我们的(节点=仅堆栈),或者他可能试图先教我们基础知识。非常感谢先生提供如此长的解释和您的时间。
0赞 arfneto 9/6/2023
好吧,正如我所说,这是有问题的,并导致更多的工作。堆栈可能是空的节点列表。在本例中,节点包含一个 .在我写的示例中,您可能会看到您的模型更接近这一点如何使事情变得更容易。基本应该不会更难。char
0赞 halfer 9/9/2023
这看起来是一个非常有帮助的帖子。不过,快速的反馈是“我不清楚你想做什么”。在 Stack Overflow 上,最好在发布之前充分了解问题空间。很大一部分新问题难以理解,其中很大一部分问题立即被任择议定书放弃,澄清请求得不到答复。因此,在这些澄清之前回复的答案可能是“错误的”,即使它通常是有帮助的。
0赞 halfer 9/9/2023
在这里回答的一个好技巧是避免在回答时考虑 OP - 而是考虑一般/未来的读者。如果这样做,关于OP的元评论,或问题的质量,或澄清/行动的要求,就会自动变得毫无意义;这很好,因为我们可能无论如何都不需要这些东西。