【问题标题】:Struggling with malloc/realloc and structures与 malloc/realloc 和结构作斗争
【发布时间】:2017-02-22 03:57:18
【问题描述】:

在使用 C 中的结构时,我真的很难理解在使用 mallocrealloc 时实际发生的情况。我正在尝试解决电话簿问题,我必须创建一个可以添加、删除、并显示条目。可以有无限数量的条目,所以我必须动态定义我的结构数组。我的删除功能还必须找到所需的条目,之后移回所有条目,然后使用realloc 释放最后一个条目。我认为这就是对realloc 的基本了解对我真正有帮助的地方。

我认为我可以正常工作的唯一部分是添加功能,我仍然不知道我是否正确设置了它 - 我只知道它在我运行时可以正常工作。如果有人在非常基本的水平可以帮助我,我将不胜感激。

这是我乱七八糟的代码:-(

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

void getEntry(int *);
typedef struct person {
    char fname[20];
    char lname[20];
    int number[10];
}person;
void getInfo(int *,int,person*);
void delInfo(int*,int,person*);
void showInfo(int*,int,person*);

int main(){

    int a=0;
    int i=0;
    int con=0;
    person* contacts=(person*)malloc(sizeof(person));
    person* pcontacts;
    pcontacts=(person*)calloc(0,sizeof(person));
    getEntry(&a);
    while (a!=4){
        switch (a){
            case 1:
                pcontacts=(person*)realloc(pcontacts,con* sizeof(person));
                getInfo(&con,i,contacts);
                break;
            case 2:
                delInfo(&con,i,contacts);
                break;
            case 3:
                showInfo(&con,i,contacts);
                break;
            default:
                printf("\n Error in response. Please try again: ");
                break;
        }
        getEntry(&a);
    }
    printf("\n Thank you for using the Phone Book Application!\n\n");
    free(contacts);
    return 0;
}

void getEntry(int *a1){
    int b;
    printf("\n\n\n Phone Book Application\n\n 1) Add Friend\n 2) Delete         Friend\n 3) Show Phone Book Entries\n 4) Exit\n\n Make a selection: ");
    scanf("%d",&b);
    *a1 = b;
 }
 void getInfo(int *con,int i,person*contacts){
    printf("\n Enter first name: ");
    scanf("%s",contacts[*con].fname);
    printf(" Enter last name: ");
    scanf("%s",contacts[*con].lname);
    printf(" Enter telephone number without spaces or hyphens: ");
    scanf("%d",contacts[*con].number);
    (*con)++;
    printf("\n Entry Saved.");
 }
 void delInfo(int *con,int i,person*contacts){
    char delfirst[20];
    char dellast[20];

    printf("\n First Name: ");
    scanf("%s",delfirst);
    printf(" Last Name: ");
    scanf("%s",dellast);

    for (i=0; i<*con;i++){
        if (delfirst==contacts[i].fname && dellast==contacts[i].lname){

        }
    }
}
void showInfo(int *con,int i,person*contacts){
    char nullstr[1]={"\0"};
    if (*con>0){
        printf("\n Current Listings:\n");
        for (i=0;i<*con;i++){
            if (strcmp(nullstr,contacts[i].fname)!=0){
                printf(" %s %s       %i\n",contacts[i].fname,contacts[i].lname,contacts[i].number);
            }
            else {};
        }
    }
    else {
        printf("\n No Entries\n");
    }
 }

【问题讨论】:

  • 如果您想要评论,请将其带到codereview.stackexchange.com 如果没有,您的问题是什么?
  • realloc documentation 的哪一部分你不明白?
  • 附带说明:将typedef 命名为p 不是很明智。为什么不typedef struct person { char fname[20]; char lname[20]; int number[10]; } person;
  • concase 1 中首次使用时为零,因此 that 对调整大小来说不是好兆头,并导致 未定义的行为 再往前走。也绝对没有理由将i 作为参数向下发送,也没有任何理由通过地址将con 传递给showInfo。您还忽略了包含&lt;string.h&gt;,这是正确声明strcmp 所必需的。

标签: c malloc


【解决方案1】:

处理这个问题的一种方法是声明一个指向person 结构的指针,然后像数组一样访问引用的内存:

person *contacts;

您可以在没有条目的情况下进行初始化:

int num_entries = 0;
contacts = NULL;

然后,当您添加条目时:

num_entries++;
contacts = realloc(contacts, sizeof(*contacts) * num_entries);

然后,您可以将新的联系信息读入新结构中,即contacts[num_entries - 1]。当您删除条目时,在将 person 结构复制到动态数组中的新位置之后(如果条目的顺序不是问题,您可以将最后一个条目复制到要删除的条目之上:contacts[delete_index] = contacts[num_entries - 1]) ,你只需这样做:

num_entries--;
contacts = realloc(contacts, sizeof(*contacts) * num_entries);

您应该检查realloc() 的返回值以处理分配错误。

realloc() 的一个很好的特性是,当传递一个空指针时,它的行为就像调用了 malloc()。因此,您可以从表示空联系人列表的空指针开始,并使用realloc() 进行所有添加和删除。关于可移植性的警告:根据GNU C 库参考手册,ISO C 之前的实现可能不支持这种行为。我认为这意味着您可以安全地将其与 ANSI C 及更高版本一起使用。我从来没有遇到过这个问题,但也许有人会插话。

【讨论】:

  • 谢谢你给我看这个。我对这些东西真的很陌生,而且看起来很简单,仅您的评论就对我有很大帮助!
  • @NLhere,我加了几句关于空指针和realloc()
【解决方案2】:

首先,让我澄清另一个答案中提出的一点:

关于可移植性的警告:根据 GNU C 库参考手册,ISO C 之前的实现可能不支持这种行为。

除非您使用几十年前的嵌入式硬件及其专有编译器,否则您很少(如果有的话)遇到至少不支持 C89 (ANSI C) 标准的编译器。


现在已经结束了,让我们继续讨论malloc()realloc()。要理解后者,我们先来看前者。

malloc()man page 声明如下:

malloc() 。 . . [和]realloc()。 . .函数分配内存。分配的内存是对齐的,以便它可以用于任何数据类型。 . . free() 函数释放通过前面的分配函数创建的分配。

这对我们有帮助。基本上,您的操作系统“拥有”物理安装在计算机中的所有可用内存。所有的 RAM,swap space 等都是由系统在kernel level 分配的。

但是,用户空间中的进程(例如您的程序)不希望/不需要管理系统中的所有内存 - 更不用说涉及的安全风险(想想我是否可以随意编辑另一个内存没有安全措施的过程!)。

因此,操作系统将管理一个进程的内存和allocate系统内存的不同部分供进程使用。

一般来说,操作系统将内存划分为所谓的块或page。简单地说,如果你把你的内存分成适当的大小(通常是 2 的指数,并且至少是 CPU 的register 大小的倍数)你会得到一些非常陡峭的optimizations,因为 CPU 只能在以下时间访问内存一定的粒度。

由于所有这些,程序本身必须向内核请求分配内存,以便程序可以使用它并确保它是唯一可以访问它的进程。

malloc() 是执行此任务的标准函数调用。当您使用长度调用malloc() 时,系统将尝试查找未使用系统内存的contiguous 范围并将其映射到进程的virtual memory 空间,存储您请求的内存地址和长度。

如果它找不到足够的内存,它只会返回NULL (0) - 你可以在手册页中阅读:)

随后,当您调用free() 时,它会在虚拟内存表中查找地址,检索您最初malloc()'d 的长度,并告诉系统该进程不再需要该内存区域。

最后一点:segmentation fault 通常是由不再分配的内存访问引起的,即使它曾经是。这就是为什么发明了诸如 RAII 之类的 C++ 范例的原因 - 只是为了减少过早释放内存的可能性,或者根本不释放它(即用完一堆在进程返回之前不会释放的内存)。


有了以上知识,realloc() 应该是小菜一碟。再一次,让我们看一下它的手册页:

realloc(ptr, size) 函数尝试将ptr 指向的分配大小更改为size,并返回ptr。如果没有足够的空间来扩大ptr 指向的内存分配,realloc() 创建一个新分配,复制ptr 指向的尽可能多的旧数据以适应新分配,释放旧数据分配,并返回一个指向已分配内存的指针。如果ptrNULL,则realloc() 等同于对malloc()size 字节调用。如果size 为零且ptr 不是NULL,则分配一个新的、最小大小的对象并释放原始对象。

这有点长,但基本上可以准确地告诉你你需要知道什么。

realloc()malloc() 非常相似,因为你传递了一个长度,但你也传递了一个以前的 malloc()'d 指针来调整大小。

简而言之,您基本上是在扩展您之前通过 malloc() 分配的内存区域。

这种混淆通常是因为realloc() 并不总是扩展初始内存区域,而且free() 甚至会扩展您传入的内存区域!

请记住我上面关于连续内存的观点:如果您要求malloc() 分配 10 个字节,而这 10 个字节恰好恰好位于已分配的另外两个内存区域之间,并且那么您想将 10 字节区域的大小调整为 20 字节——会发生什么?

由于内存区域必须是连续的,这意味着它必须为内存找到一个可以连续容纳所有 20 个字节的新位置!

在这种情况下,realloc() 可以很好地找到具有足够空间的新区域,从原始内存区域复制所有旧数据(由您提供的指针 realloc() 指向),释放旧区域,然后返回新区域的地址。

这就是为什么你必须总是存储realloc()的返回地址:

char *ptr = malloc(100);
ptr = realloc(ptr, 400); // not guaranteed the first and
                         // second addresses will be the same!

【讨论】:

  • @Qix--我不确定你在揭穿什么。 glibc 手册指出旧的实现可能不支持realloc() 的空指针参数行为。我确实建议这通常不是问题,但我发现了解这些可能性是件好事。不过,很好的一点是,强调存储对realloc() 的调用返回的指针的必要性。
  • @DavidBowling 现在几乎不可能找到 pre-ISO 编译器。 :) 这就是我的观点,而不是试图揭穿realloc() 在超级旧编译器上的行为会有所不同的事实。
  • @Qix——这是一个公平的观点;我只是对你在揭穿我所说的话而不是澄清我所说的话的想法提出异议。我喜欢你对malloc()relloc() 的彻底讨论,特别希望我能想到你必须保存返回的指针。我见过有人认为重新分配的内存必须与原始内存位于同一位置,但没有做到这一点。仅这一点就足以让我有理由为您的答案投票。
猜你喜欢
  • 2021-01-15
  • 1970-01-01
  • 2012-09-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-12-12
  • 2020-03-28
相关资源
最近更新 更多