C数组的结构分段错误

C array of structs segmentation fault

我正在尝试创建一个动态结构数组,我可以成功地向其中添加一个结构。但是我添加的任何更多结构都会导致分段错误。这是我的代码:

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

#define PEOPLE_BLOCK 4

struct Person {
    char *first_name;
    char *last_name;
    unsigned int age;
};

int add_person(struct Person **people, size_t *people_size, size_t *population, struct Person p) {

    if ((sizeof(struct Person) * *population) > *people_size) {
        return -1;
    }

    if ((sizeof(struct Person) * (*population + 1)) >= *people_size) {
        *people_size = *people_size + sizeof(struct Person) * PEOPLE_BLOCK;
        *people = realloc(*people, *people_size);
        if (!*people) {
            return -1;
        }
    }

    *people[*population] = p;
    ++*population;

    return 0;
}

int main(int argc, char const *argv[]) {

    size_t population;
    size_t people_size;
    struct Person *people, timn, batman;

    population = 0;
    people_size = sizeof(struct Person) * PEOPLE_BLOCK;
    people = malloc(people_size);

    timn.first_name = "Timn";
    timn.last_name = "Timothy";
    timn.age = 38;
    add_person(&people, &people_size, &population, timn);

    printf("Person 0's first name: %s\n", people[0].first_name);

    batman.first_name = "Bat";
    batman.last_name = "Man";
    batman.age = 42;
    add_person(&people, &people_size, &population, batman);

    printf("Person 1's first name: %s\n", people[1].first_name);

    free(people);

    return 0;
}

如果能帮助我解释为什么会发生这种情况,我将不胜感激!

一个问题是 add_person() 的最后一个参数,即参数“(struct Person) p”。当 'timn' 和 'batman' 被传递到 add_person() 函数时,它们作为原始结构的 copy 被传递。在 add_person() 结构中,该数据实际上 在堆栈上 并且在函数范围之外是易变的。尝试将最后一个参数更改为指针。

问题出在这一行:

*people[*population] = p;

改为:

(*people)[*population] = p;

为什么需要括号?

编译器有rules of operator precedence。应用它们时,它会将您的代码视为:

*(people[*population]) = p;

这不是您想要的。给定一个指向指针的指针 Type **pp,

*pp[n] = value;

的意思是“获取从 pp 开始的第 n 个指针,并在从指针所持有的地址取消引用的位置分配 value。换句话说,它本质上意味着这个:

Type *p = pp[n];
*p = value;

真正想要的是这个:

Type *p = *pp;
p[n] = value;

这就是 (*pp)[n],区分指针到指针的取消引用,给你的。否则,您将使用无效指针,从而导致您的错误。

不确定这个答案是否有帮助,但无论如何。 我不明白你的代码,你想做什么。

你直接用元素个数,第一人称指针,最大元素个数。你可能会遇到很多问题。

您将文字字符串直接存储在结构中,这意味着在实际情况下(不使用文字)会导致内存泄漏。

这是我的看法。出于测试原因,我将 PEOPLE_BLOCK 变小了。 希望这有帮助。

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

#define PEOPLE_BLOCK 2

typedef struct _Person {
    char *first_name;
    char *last_name;
    unsigned int age;
} Person;

typedef struct _VectorPeople {
    Person * people;
    size_t num;
    size_t max;
} VectorPeople;

void init(VectorPeople *v)
{
    v->max = PEOPLE_BLOCK;
    v->num = 0;
    v->people = (Person *) malloc( sizeof(Person) * v->max );
}

void clear(VectorPeople *v)
{
    // Clear persons
    Person * it = v->people;
    while( ( it - v->people ) < v->num ) {
        free( it->first_name );
        free( it->last_name );

        ++it;
    }

    // Clear vector
    v->max = v->num = 0;
    free( v->people );
    v->people = NULL;
}

void add(VectorPeople *v, Person *p)
{
    // Reserve
    if ( v->num >= v->max ) {
        v->max += PEOPLE_BLOCK;

        // Realloc
        v->people = realloc( v->people, v->max * sizeof(Person) );
        if ( v->people == NULL ) {
            exit( -1 );
        }
    }

    // Copy strings
    p->first_name = strdup( p->first_name );
    p->last_name = strdup( p->last_name );

    // Insert
    v->people[ ( v->num )++ ] = *p;
}

int main(int argc, char const *argv[]) {

    VectorPeople vp;
    Person timn;
    Person batman;
    Person bond;
    Person superman;

    init( &vp );

    timn.first_name = "Timn";
    timn.last_name = "Timothy";
    timn.age = 38;
    add( &vp, &timn );

    batman.first_name = "Batn";
    batman.last_name = "Man";
    batman.age = 42;
    add( &vp, &batman );

    bond.first_name = "James";
    bond.last_name = "Bond";
    bond.age = 45;
    add( &vp, &bond );

    superman.first_name = "Super";
    superman.last_name = "Man";
    superman.age = 45;
    add( &vp, &superman );

    int i = 0;
    for(; i < vp.num; ++i ) {
        printf( "Person: %s, %s.\n", vp.people[ i ].last_name, vp.people[ i ].first_name );
    }
    clear( &vp );
    return 0;
}

您的代码中存在一些错误。要记住一件事,当你动态分配内存时,负责跟踪它并在你不再需要它时释放它(否则,你会像筛子一样泄漏内存).

在您的代码中,您尝试创建一个结构数组,其中包含指向字符数组的指针。 char * 指针未分配,不能简单地以您尝试的方式分配。 strdup 可以提供帮助,但是您刚刚分配了内存,所以 free 当您完成它时。

尝试分配具有不同(未知)长度 first_namelast_name 的结构数组需要您跟踪每个分配。从某种意义上说,您最好将 people 声明为 pointer to pointer to Person 这允许对 people 进行迭代,而不必将人口存储在某个地方,从而允许您迭代直到第一个 NULL 指针遇到了。

同样,为你的结构创建一个typedef可以大大减少你写sizeof (struct Person)的次数。它使代码保持整洁,并帮助您思考指针的迷雾。

这是一个使用指针到指针到结构的示例,我认为您打算这样做。下面是仅使用指向结构的指针的实现。评估两者并决定您更喜欢哪种实现:

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

#define MAXPOP 128

typedef struct {
    char *first_name;
    char *last_name;
    unsigned char age;
} Person;

Person *add_person (Person ***ppl, Person p, size_t *pop, size_t *max);
Person **realloc_person (Person **ppl, size_t *n);
void free_person (Person *p);
void free_person_names (Person *p);

int main (void) {

    size_t population = 0;
    size_t maxp = MAXPOP;
    size_t i = 0;
    Person timn, batman;
    Person **people = calloc (MAXPOP, sizeof *people);

    if (!people) {
        fprintf (stderr, "error: virtual memory exhausted.\n");
        return 1;
    }

    timn.first_name = strdup ("Timn");
    timn.last_name = strdup ("Timothy");
    timn.age = 38;
    add_person (&people, timn, &population, &maxp);
    free_person_names (&timn);

    printf("\nPerson 0\n first name: %s\n last name : %s\n age : %hhu\n",
            people[0]->first_name, people[0]->last_name, people[0]->age);

    batman.first_name = strdup ("Bat");
    batman.last_name = strdup ("Man");
    batman.age = 42;
    add_person (&people, batman, &population, &maxp);
    free_person_names (&batman);

    printf("\nPerson 1\n first name: %s\n last name : %s\n age : %hhu\n",
            people[1]->first_name, people[1]->last_name, people[1]->age);

    for (i = 0; i < population; i++)
        free_person (people[i]);
    free (people);

    return 0;
}

/* add a person to an array of pointers to Person */
Person *add_person (Person ***ppl, Person p, size_t *pop, size_t *max)
{
    if (*pop == *max)
        *ppl = realloc_person (*ppl, max);

    if (!((*ppl)[*pop] = malloc (sizeof ***ppl)))
        return NULL;

    size_t i = (*pop)++;
    (*ppl)[i]-> first_name = strdup (p.first_name);
    (*ppl)[i]-> last_name = strdup (p.last_name);
    (*ppl)[i]-> age = p.age;

    return (*ppl)[i];
}

/* realloc an array of pointers to Person setting memory to 0. */
Person **realloc_person (Person **ppl, size_t *n)
{
    Person **tmp = realloc (ppl, 2 * *n * sizeof *ppl);
    if (!tmp) {
        fprintf (stderr, "Error: struct reallocation failure.\n");
        // return NULL;
        exit (EXIT_FAILURE);
    }
    ppl = tmp;
    memset (ppl + *n, 0, *n * sizeof *ppl); /* memset new ptrs 0 */
    *n *= 2;

    return ppl;
}

/* free memory for a Person */
void free_person (Person *p)
{
    if (!p) return;
    if (p->first_name) free (p->first_name);
    if (p->last_name) free (p->last_name);
    free (p);
}

/* free only names of Person (for temp structs) */
void free_person_names (Person *p)
{
    if (!p) return;
    if (p->first_name) free (p->first_name);
    if (p->last_name) free (p->last_name);
}

注意: 已更新以更正 ppl 重新分配的起始地址。


仅使用人物数组

虽然本质上与使用指向 Person 的指针不同,但使用指向 Person 的简单指针消除了迭代数组的能力,直到 NULL 或(空)遇到指针。以下是仅使用 Person:

数组的相同代码的实现
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAXPOP 128

typedef struct {
    char *first_name;
    char *last_name;
    unsigned char age;
} Person;

Person *add_person (Person **ppl, Person p, size_t *pop, size_t *max);
Person *realloc_person (Person *ppl, size_t *n);
void free_person_names (Person p);

int main (void) {

    size_t population = 0;
    size_t maxp = MAXPOP;
    size_t i = 0;
    Person timn, batman;
    Person *people = calloc (MAXPOP, sizeof *people);

    if (!people) {
        fprintf (stderr, "error: virtual memory exhausted.\n");
        return 1;
    }

    timn.first_name = strdup ("Timn");
    timn.last_name = strdup ("Timothy");
    timn.age = 38;
    add_person (&people, timn, &population, &maxp);
    free_person_names (timn);

    printf("\nPerson 0\n first name: %s\n last name : %s\n age : %hhu\n", 
            people[0].first_name, people[0].last_name, people[0].age);

    batman.first_name = strdup ("Bat");
    batman.last_name = strdup ("Man");
    batman.age = 42;
    add_person (&people, batman, &population, &maxp);
    free_person_names (batman);

    printf("\nPerson 1\n first name: %s\n last name : %s\n age : %hhu\n", 
            people[1].first_name, people[1].last_name, people[1].age);

    for (i = 0; i < population; i++)
        free_person_names (people[i]);
    free (people);

    return 0;
}

/* add a person to an array of pointers to Person */
Person *add_person (Person **ppl, Person p, size_t *pop, size_t *max)
{
    if (*pop == *max)
        *ppl = realloc_person (*ppl, max);

    size_t i = (*pop)++;
    (*ppl)[i].first_name = strdup (p.first_name);
    (*ppl)[i].last_name = strdup (p.last_name);
    (*ppl)[i].age = p.age;

    return ppl[i];
}

/* realloc an array Person setting memory to 0. */
Person *realloc_person (Person *ppl, size_t *n)
{
    Person *tmp = realloc (ppl, 2 * *n * sizeof *ppl);
    if (!tmp) {
        fprintf (stderr, "Error: struct reallocation failure.\n");
        // return NULL;
        exit (EXIT_FAILURE);
    }
    ppl = tmp;
    memset (ppl + *n, 0, *n * sizeof *ppl); /* memset new ptrs 0 */
    *n *= 2;

    return ppl;
}

/* free only names of Person (for temp structs) */
void free_person_names (Person p)
{
    if (p.first_name) free (p.first_name);
    if (p.last_name) free (p.last_name);
}

输出

$ ./bin/struct_add_person

Person 0
 first name: Timn
 last name : Timothy
 age : 38

Person 1
 first name: Bat
 last name : Man
 age : 42