如何理解分段错误?

时间:2016-11-03 00:51:53

标签: c

我第一次尝试在C中使用结构和指针。即使程序编译完美,我也不能理解为什么我会一直遇到分段错误。我也对内存分配感到困惑。我们每次总是在内存分配中使用强制转换吗?

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



#define HOW_MANY 7

char *names[HOW_MANY]= {"Adam", "James", "Matt", "Affleck", "Benedict", "Kayne",
    "Evans"};
int ages[HOW_MANY]= {22, 24, 46, 56, 21, 32, 30};

/* declare your struct for a person here */
typedef struct{
    char name[HOW_MANY];
    int age;
}person;


static void insert(person *people[], char *name, int age)
{
    static int nextfreeplace = 0;
    // Allocating memory here
    people = malloc(sizeof(person));

    if (people == NULL) {
        printf("Couldn't allocate memory");
        exit(-1);
    }

    strcpy((*people[nextfreeplace]).name,name);

    (*people[nextfreeplace]).age = age;

    nextfreeplace++;
}

int main(int argc, char **argv)
{
    person *people[HOW_MANY];

    for (int i = 0; i < HOW_MANY ; i++)
    {
        insert (people, names[i], ages[i]);
    }

    /* print the people array here*/
    for (int i = 0; i < HOW_MANY ; i++)
    {

        printf("The person's name is %s and the age is %d.\n",(*people[i]).name,(*people[i]).age);
    }

    for (int i = 0; i < HOW_MANY ; i++)
    {
        free(people[i]);
    }

    return 0;
}

这是分配内存的正确方法吗?

3 个答案:

答案 0 :(得分:1)

通过C代码保持相同的逻辑

调用 insert ,提供指向要分配内存的位置的指针,即 people 数组中指针位置的地址;在函数for循环中:

insert (&people[i], names[i], ages[i]);

因此在插入

static void insert(person **people, char *name, int age)
{
    *people = malloc(sizeof(person));

    if (*people == NULL) {
        printf("Couldn't allocate memory");
        exit(-1);
    }

    strcpy((*people)->name,name);

    (*people)->age = age;
}

答案 1 :(得分:0)

好吧,这里似乎有2个错误:

  • Eugene指出的一个是malloc,你应该将分配的内存存储在适当的位置,这是指向&#34; person&#34;的指针数组的一个元素。结构体;你需要:

    people[nextfreeplace] = malloc(sizeof(person));
    
  • 第二个,名称太小,你应该分配更多的字符或截断它...(更新:BLUEPIXY在我写回复时发布了代码...看到他的代码,不需要我再写一次......)。简而言之&#34;本尼迪克特&#34;有8个字符,所以...你需要至少9个字符分配给&#34; name&#34;在struct中(字符串终止符也需要一个字符)。

    typedef struct{
      char name[9];  // 8 is already not good
      int age;
    }person;
    

如果你解决它们,你的代码就可以正常运行......

答案 2 :(得分:0)

我使用两个非常重要的工具来了解内存问题。第一个是编译器警告。它们默认情况下不启用,您必须使用-Wall打开它们。你的程序没有任何警告,好。

其次是使用内存检查器,这将寻找内存问题。这避免了必须进行大量仔细研究。我使用valgrind来显示内存违规以及堆栈。

==90314== Conditional jump or move depends on uninitialised value(s)
==90314==    at 0x1013AD570: _platform_memmove$VARIANT$Nehalem (in /usr/lib/system/libsystem_platform.dylib)
==90314==    by 0x10112A421: stpcpy (in /usr/lib/system/libsystem_c.dylib)
==90314==    by 0x10119DBED: __strcpy_chk (in /usr/lib/system/libsystem_c.dylib)
==90314==    by 0x100000E9A: insert (test.c:31)
==90314==    by 0x100000D8A: main (test.c:44)
==90314== 
==90314== Use of uninitialised value of size 8
==90314==    at 0x1013AD5C0: _platform_memmove$VARIANT$Nehalem (in /usr/lib/system/libsystem_platform.dylib)
==90314==    by 0x10112A421: stpcpy (in /usr/lib/system/libsystem_c.dylib)
==90314==    by 0x10119DBED: __strcpy_chk (in /usr/lib/system/libsystem_c.dylib)
==90314==    by 0x100000E9A: insert (test.c:31)
==90314==    by 0x100000D8A: main (test.c:44)
==90314== 
==90314== Invalid write of size 1
==90314==    at 0x1013AD5C0: _platform_memmove$VARIANT$Nehalem (in /usr/lib/system/libsystem_platform.dylib)
==90314==    by 0x10112A421: stpcpy (in /usr/lib/system/libsystem_c.dylib)
==90314==    by 0x10119DBED: __strcpy_chk (in /usr/lib/system/libsystem_c.dylib)
==90314==    by 0x100000E9A: insert (test.c:31)
==90314==    by 0x100000D8A: main (test.c:44)
==90314==  Address 0x0 is not stack'd, malloc'd or (recently) free'd
==90314== 
==90314== 
==90314== Process terminating with default action of signal 11 (SIGSEGV)
==90314==  Access not within mapped region at address 0x0
==90314==    at 0x1013AD5C0: _platform_memmove$VARIANT$Nehalem (in /usr/lib/system/libsystem_platform.dylib)
==90314==    by 0x10112A421: stpcpy (in /usr/lib/system/libsystem_c.dylib)
==90314==    by 0x10119DBED: __strcpy_chk (in /usr/lib/system/libsystem_c.dylib)
==90314==    by 0x100000E9A: insert (test.c:31)
==90314==    by 0x100000D8A: main (test.c:44)

这表示您拨打strcpy时出现问题。

strcpy((*people[nextfreeplace]).name,name);

其中一个论点是未初始化的,name*people[nextfreeplace]).namename来自names,所以可能不是。

问题在于如何分配peoplepeople是指向person指针数组的指针。但是你可以用一个人的记忆来打击它。

people = malloc(sizeof(person));

C将允许您在没有警告的情况下执行此操作,因为malloc会返回void *,它会愉快地变形为任何指针类型。

相反,你应该为一个人分配空间,然后将指针指向人。

static void insert(person *people[], char *name, int age)
{
    static int nextfreeplace = 0;

    // Allocating memory here
    person *human = malloc(sizeof(person));
    if (human == NULL) {
        printf("Couldn't allocate memory");
        exit(-1);
    }

    strcpy(human->name, name);
    human->age = age;
    people[nextfreeplace] = human;

    nextfreeplace++;
}

这也指出您应该为PersonPerson_t等类型命名,以避免与内置类型和良好变量名称冲突。

这揭示了下一个问题,又是strcpy。它总是strcpy

==5111== Process terminating with default action of signal 6 (SIGABRT)
==5111==    at 0x101269F36: __pthread_sigmask (in /usr/lib/system/libsystem_kernel.dylib)
==5111==    by 0x10117876C: __abort (in /usr/lib/system/libsystem_c.dylib)
==5111==    by 0x1011786ED: abort (in /usr/lib/system/libsystem_c.dylib)
==5111==    by 0x101178855: abort_report_np (in /usr/lib/system/libsystem_c.dylib)
==5111==    by 0x10119EA0B: __chk_fail (in /usr/lib/system/libsystem_c.dylib)
==5111==    by 0x10119E9DB: __chk_fail_overflow (in /usr/lib/system/libsystem_c.dylib)
==5111==    by 0x10119EC28: __strcpy_chk (in /usr/lib/system/libsystem_c.dylib)
==5111==    by 0x100000E8F: insert (test.c:31)
==5111==    by 0x100000D8A: main (test.c:44)

strcpy容易溢出缓冲区。在person->name中是否分配了足够的空间?我们看看......

typedef struct{
    char name[HOW_MANY];
    int age;
}person;

不。 HOW_MANY是7.认为name的错误是名单。相反,它的名称可以是多久。

如果我们将其改为像32这样合理的东西,它就可以了!

The person's name is Adam and the age is 22.
The person's name is James and the age is 24.
The person's name is Matt and the age is 46.
The person's name is Affleck and the age is 56.
The person's name is Benedict and the age is 21.
The person's name is Kayne and the age is 32.
The person's name is Evans and the age is 30.

Valgrind很高兴,但由于代码中的静态分配,它容易受到缓冲区溢出的影响。如果其中一个名字是“Johnathan Jacob Jingleheimerschmit”,你就会有缓冲区溢出。

你有两个选择。静态分配比你需要更多的内存,或者使用动态内存。

一般......

避免静态内存分配。

这是一个非常糟糕的习惯。它会引发各种内存溢出。习惯于动态分配和重新分配内存,或使用像链表和树一样自然增长的结构。

使用字符串库。

C中的字符串只是一场噩梦。像Gnome Lib这样的通用C库提供了functions for manipulating strings safely和他们自己的improved String type

始终为您的结构编写newdestroy

为结构分配和释放内存可能会变得棘手。最好将所有这些都放在他们自己的功能中,而不是依靠你的代码来做到这一点。即使你认为它是微不足道的,它也可能在以后变得微不足道。

typedef struct {
    char *name;
    int age;
} Person_t;

/* So we don't have to check if a thing is null before freeing it */
static void free_if(void *thing) {
    if( thing != NULL ) {
        free(thing);
    }
}

static Person_t* Person_new() {
    /* calloc() is used here to 0 the structure so we don't use garbage */
    Person_t *person = calloc(1, sizeof(Person_t));
    if (person == NULL) {
        fprintf(stderr, "Couldn't allocate memory for Person_t: %s", strerror(errno));
        exit(-1);
    }

    return person;
}

static void Person_destroy(Person_t *person) {
    free_if(person->name);
    free(person);
}

请注意,我已经开始使用动态分配的内存作为名称。这将我们带到下一点。

编写处理结构内存的函数。

如果你需要在你的结构中分配和复制东西,那就把它变成一个能够处理所有这些东西的函数。

static void Person_set_name(Person_t *person, char *name) {
    free_if(person->name);
    person->name = malloc( (strlen(name) + 1) * sizeof(char) );
    strcpy( person->name, name );
}

将结构视为对象,为它们编写方法。

如果您熟悉其他语言的面向对象编程,您可以看到这一切都在哪里。将结构视为对象。为它写“方法”。在这些方法中完成所有工作,而不是使用结构的代码。

这将管理结构的所有工作封装成您可以轻松构建,管理和测试的部分。