尝试在结构上使用scanf时出现分段错误

Nov*_*ane 3 c malloc struct scanf segmentation-fault

我对c很陌生,此刻我也非常沮丧.这是我的代码:

typedef struct {

char* fName;
char* lName;
char* pNum;
char* address;
char* email;
} contactInfo;

void addContact(){
contactInfo *contact;
contact = (contactInfo *) malloc (sizeof(contactInfo));

printf("\n[Add a contact]\nFirst Name: ");
scanf("%s", contact->fName);
printf("%s", contact->fName);
}
Run Code Online (Sandbox Code Playgroud)

出于某种原因,当我输入scanf的值时,它会给我一个分段错误.如果我尝试在contact-> fName前添加&,我也会收到错误.

代码有什么问题?

Tim*_*nes 13

首先,不要担心 - 开始C的沮丧是正常的:)

既然你说你是初学者,我写了一个很长的答案,解释了你可能想要做的其他一些改进.对不起,如果我介绍一些你已经知道的事情.这是一个总结:

  1. 你需要为char*s指定一些空间来指向(这是造成崩溃的原因)
  2. 确保检查malloc的返回值
  3. 确保要求scanf()只读取字符串中可以容纳的字符数.
  4. 无需从malloc转换返回值.
  5. 记住free()你做过任何动画片的事情.

你需要为char*s指定一些空间来指向

在C中,a char*表示"指向char的指针".char*通常用于字符串,因为您可以索引指针,就像它们是数组一样 - 例如,假设:

 char *a = "Hello";
Run Code Online (Sandbox Code Playgroud)

然后, a[1]意思是" 在这个案例中,charchar指向的第一个;a'e'

你有这个代码:

contactInfo *contact;
contact = (contactInfo *) malloc (sizeof(contactInfo));
Run Code Online (Sandbox Code Playgroud)

此时,您已经声明了一个指向contactInfo结构的指针,并为其分配了正确大小的内存.但是,结构中的指针当前没有指向任何东西 - 所以你的程序在调用时会崩溃scanf().您还需要为要阅读的字符分配空间,例如:

contact->fName = malloc(sizeof(char) * 10);
Run Code Online (Sandbox Code Playgroud)

将为10个字符分配空间.您需要为char*结构中的每个人执行此操作.

我不希望你担心的几个副作用:

  • 在C中,sizeof(char)总是1,所以你可以写malloc(10),但在我看来,它的可读性较差.
  • 你也可以这样做:

    contact->fName = malloc(sizeof(*(contact->fName)) * 10);
    
    Run Code Online (Sandbox Code Playgroud)

    这对于类型的变化很有用fName- 你总是会为10 fName个点分配足够的空间.

确保检查malloc的返回值

现在回到正轨 - 您还应该检查返回值malloc():

contact->fName = malloc(sizeof(char) * 10);
if(contact->fName == NULL) {
   // Allocation failed 
}
Run Code Online (Sandbox Code Playgroud)

在某些情况下,您可能能够从失败的分配中恢复(例如,尝试再次分配,但要求更少的空间),但要从以下开始:

contact->fName = malloc(sizeof(char) * 10);
if(contact->fName == NULL) {
   printf(stderr,"Allocation of contact->fName failed");
   exit(EXIT_FAILURE);
}
Run Code Online (Sandbox Code Playgroud)

可能没问题.许多程序员会编写一个包装器来malloc()检查这个错误,以便他们不再需要担心它.

确保您只要求scanf()在字符串中读取尽可能多的字符.

请注意,一旦你分配了10个字符fName,scanf()可能会读取太多字符.您可以通过写入来明确告诉scanf限制,"%Ns"其中N是字符串中的最大字符数(结尾处的空终止符减去1).所以,如果你已经分配了10个字符,那么你应该写:

scanf("%9s", contact->fName);
Run Code Online (Sandbox Code Playgroud)

无需从malloc转换返回值.

最后一点 - 你不需要在C中强制转换malloc的返回值,所以我可能会写:

 contact =  malloc (sizeof(contactInfo));
Run Code Online (Sandbox Code Playgroud)

记住free()任何你拍摄过的东西

您可能已经这样做了,但每次做malloc()任何事情时,请确保free()在完成后您的代码中有相应的内容.这告诉操作系统它可以恢复内存.所以,如果你有某个地方

 contact =  malloc (sizeof(contactInfo));
Run Code Online (Sandbox Code Playgroud)

稍后,当您完成该联系后,您将需要具有以下内容:

 free(contact);
Run Code Online (Sandbox Code Playgroud)

避免内存泄漏.

一旦你释放了某些东西,你就不能再访问了它.所以,如果你在联系人中使用了malloced字符串,你必须首先释放它们:

 free(contact->fName);  // doing this in the other order might crash
 free(contact);  
Run Code Online (Sandbox Code Playgroud)

关于免费的一些要记住的事情:

  1. 你不能两次免费.为了避免这种情况,一个好的做法是写:

     if(contact != NULL) free(contact); 
     contact = NULL;
    
    Run Code Online (Sandbox Code Playgroud)

    如果以这种方式编写,那么在创建它们时,您还需要将所有指针初始化为NULL.当您创建具有指针的结构时,一种简单的方法是使用calloc()而不是malloc()创建结构,因为calloc()返回始终为零的内存.

  2. 程序退出时,所有内存都将释放回操作系统.这意味着您在技术上不需要free()在程序的整个生命周期中使用的东西.但是,我建议养成释放你所有东西的习惯,否则你会忘记有一天它很重要.


进一步改进

作为评论者指出另一个答案,使用幻数(代码中硬编码的数字)通常是不好的做法.在上面给出的例子中,我将"10"硬编码到程序中作为字符串的大小.但是,最好做以下事情:

#define FNAME_MAX_LENGTH 10
Run Code Online (Sandbox Code Playgroud)

然后去:

malloc(sizeof(char) * FNAME_MAX_LENGTH);
Run Code Online (Sandbox Code Playgroud)

这样做的好处是,如果您需要在任何地方更改字符串的大小,您可以在一个地方更改它.它还可以防止您意外地在一个地方输入100或1,从而导致潜在的严重,难以发现的错误.

当然,既然你有一个#define长度,你需要更新scanf()我们指定长度的调用.但是,由于scanf()需要长度为1,因此您无法使用#define指定长度(至少不能以任何可读的方式).

因此,您可能感兴趣fgets(),它读取指定的长度-1(或直到行的末尾 - 以先到者为准).然后你可以这样做:

fgets(contact->fName,FNAME_MAX_LENGTH,stdin);
Run Code Online (Sandbox Code Playgroud)

而不是scanf()电话.做出这种改变的另一个好理由是,这scanf()可能是一种痛苦.

所以,除了上面的摘要:

  1. 使用#define作为字符串的长度可以避免出现问题并使以后更容易更改代码.
  2. fgets()比字符串长度更容易使用scanf(),并且更容易使用#define.