【问题标题】:Segmentation Fault when trying to use scanf on a struct尝试在结构上使用 scanf 时出现分段错误
【发布时间】:2012-04-30 13:57:27
【问题描述】:

我对 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);
}

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

代码有什么问题?

【问题讨论】:

  • 您应该为结构中的所有char * 分配内存。
  • 永远不要在 C 中对 malloc 的结果进行类型转换。阅读 thisthis

标签: c struct segmentation-fault malloc scanf


【解决方案1】:

您应该为结构中的所有char * 分配内存。

例如:

contact->fName =  malloc(sizeof(char) * 10);

另外,你应该检查malloc()的返回值

【讨论】:

  • 我快要支持这个了。但是 OP 显然是一个新手,所以如果你展示一个如何检查 malloc 的返回值的例子,我会赞成,并谈论不要将像 10 这样的幻数直接放在你的代码中(并使用 #define反而)。将 OP 指向 fgets 的奖励积分可以保护您免受 scanf 无所作为的缓冲区溢出漏洞的影响。
  • @AdamMihalcin 好。感谢提醒help 应该是完整的!
  • malloc的返回值不需要转换,(int)也不需要转换。
  • @SangeethSaravanaraj malloc 结果的类型转换不仅没用,而且很危险。阅读thisthis。您应该删除类型转换。
【解决方案2】:

首先,别担心 - 以 C 开头的挫折是正常的 :)

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

  1. 您需要为char*s 分配一些空间以指向(这是导致崩溃的原因)
  2. 确保检查 malloc 的返回值
  3. 确保要求 scanf() 只读取字符串中可以容纳的字符数。
  4. 无需从 malloc 转换返回值。
  5. 记得 free() 任何你已经 malloc 编辑过的东西。

您需要为char*s 分配一些空间以指向

在 C 中,char* 表示“指向字符的指针”。 char* 通常用于字符串,因为您可以像索引数组一样索引指针 - 例如,假设:

 char *a = "Hello";

那么,a[1] 表示“a 指向的字符之后的第一个char,在本例中为'e'

你有这个代码:

contactInfo *contact;
contact = (contactInfo *) malloc (sizeof(contactInfo));

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

contact->fName = malloc(sizeof(char) * 10);

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

我不想让你太担心的几个旁白:

  • 在 C 中,sizeof(char) 始终为 1,因此您可以写成 malloc(10),但在我看来,它的可读性较差。
  • 你也可以这样做:

    contact->fName = malloc(sizeof(*(contact->fName)) * 10);
    

    这对于 fName 类型的更改是稳健的 - 您将始终为 fName 指向的任何 10 个分配足够的空间。

确保检查 malloc 的返回值

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

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

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

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

应该没问题。许多程序员会为malloc() 编写一个包装器,为他们进行错误检查,这样他们就不必再担心了。

确保您只要求scanf() 读取字符串中可以容纳的尽可能多的字符。

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

scanf("%9s", contact->fName);

无需从 malloc 转换返回值。

最后一点 - you don't need to cast the return value of malloc in C,所以我可能会写:

 contact =  malloc (sizeof(contactInfo));

记住free() 你分配的任何东西

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

 contact =  malloc (sizeof(contactInfo));

稍后,当您处理完该联系人后,您将需要以下内容:

 free(contact);

避免内存泄漏。

一旦你释放了一些东西,你就不能再访问它了。因此,如果您在联系人中分配了字符串,则必须首先释放它们:

 free(contact->fName);  // doing this in the other order might crash
 free(contact);  

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

  1. 你不能两次释放任何东西。为避免这种情况,一个好的做法是编写:

     if(contact != NULL) free(contact); 
     contact = NULL;
    

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

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


进一步改进

正如评论者在另一个答案中指出的那样,使用幻数(代码中硬编码的数字)通常是不好的做法。在我上面给你的例子中,我把“10”硬编码到程序中作为字符串的大小。但是,最好执行以下操作:

#define FNAME_MAX_LENGTH 10

然后再去:

malloc(sizeof(char) * FNAME_MAX_LENGTH);

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

当然,既然您已经有了一个#define 的长度,您需要更新我们指定长度的scanf() 调用。但是,由于 scanf() 需要长度 - 1,您将无法使用 #define 指定长度(至少,不是以任何可读的方式)。

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

fgets(contact->fName,FNAME_MAX_LENGTH,stdin);

而不是scanf() 调用。进行此更改的另一个充分理由是scanf() 可以是kind of a pain

所以,除了上面的总结:

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

【讨论】:

  • ...不要忘记 free() 的。
  • @PeterMiehle 哦,好吧;)我在我的百万字答案中添加了一个关于释放的部分:)
  • 哇,这超出了我的要求,非常感谢!
【解决方案3】:

根据@AdamMihalcin 的建议,我已经给出了一个几乎完整的代码,希望可以作为参考。

注意几点:

  1. 应该检查malloc()的返回值。因为malloc() 从堆中获取内存,如果没有足够的内存,那么malloc() 可能会返回NULL。要了解更多关于 malloc 的信息,您可以阅读其手册页 - man malloc

  2. 所有malloc'ed 内存必须是free'ed。

  3. Difference between scanf() and fgets()C - scanf() vs gets() vs fgets() 解释了为什么 fgets()scanf() 更受欢迎

代码如下:

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

/* 
define the length of each filed 
in the contactInfo struct 
*/
#define L_fName     10
#define L_lName     10
#define L_pNum      10
#define L_address   25  
#define L_email     15

typedef struct {
    char* fName;
    char* lName;
    char* pNum;
    char* address;
    char* email;
} contactInfo;

contactInfo * release_ci(contactInfo * contact) 
{
    if (contact == NULL) return NULL;
    free(contact->fName);
    free(contact->lName);
    free(contact->pNum);
    free(contact->address);
    free(contact->email);
    free(contact);
    return NULL;
}

contactInfo * alloc_ci()
{
    contactInfo *contact;
    if ((contact = malloc(sizeof(contactInfo))) == NULL) {
        printf("ERROR: unable to allocate memory for contactInfo \n");
        goto free_and_fail;
    }

    if ((contact->fName = malloc(sizeof(char) * L_fName)) == NULL) {
        printf("ERROR: unable to allocate memory for fName\n");
        goto free_and_fail;
    }

    if ((contact->lName = malloc(sizeof(char) * L_lName)) == NULL) {
        printf("ERROR: unable to allocate memory for lName\n");
        goto free_and_fail;
    }

    if ((contact->pNum = malloc(sizeof(char) * L_pNum)) == NULL) {
        printf("ERROR: unable to allocate memory for pNum\n");
        goto free_and_fail;
    }

    if ((contact->address = malloc(sizeof(char) * L_address)) == NULL) {
        printf("ERROR: unable to allocate memory for address\n");
        goto free_and_fail;
    }

    if ((contact->email = malloc(sizeof(char) * L_email)) == NULL) {
        printf("ERROR: unable to allocate memory for email\n");
        goto free_and_fail;
    }

    return contact;

free_and_fail:
    release_ci(contact);
    return NULL;
}

int main()
{
    contactInfo *ci = alloc_ci();

    if (!ci) return -1;

    printf("Enter fName     : ");
    fgets (ci->fName,   L_fName,    stdin);    
    printf("Enter lName     : ");
    fgets (ci->lName,   L_lName,    stdin);    
    printf("Enter pNum      : ");
    fgets (ci->pNum,    L_pNum,     stdin);    
    printf("Enter address   : ");
    fgets (ci->address, L_address,  stdin);    
    printf("Enter email     : ");
    fgets (ci->email,   L_email,    stdin);    

    /* TODO: validation for all the input fields */

    release_ci(ci);
    return 0;
}

【讨论】:

  • 关闭(并且仍然有效 +1)。但是,要在使用free_and_fail 时做到防弹,您需要calloc 联系,而不是malloc。每当您malloc 时,联系人都会被垃圾填满,因此您可能无法分配contact-&gt;pNum,发现contact-&gt;email 不为空,然后尝试释放那些垃圾字节指向的任何内容。如果您calloc,则联系人将被填充为零,因此如果contact-&gt;pNum 分配失败,calloc-&gt;email 实际上将为空。
  • 您的意思是发布新帖子而不是编辑您的旧答案吗?您的清理代码可能会更好(free(NULL) 是无操作的,因此无需先检查其参数;alloc_ci 可以利用 release_ci 而不是复制代码)。
  • @AdamMihalcin:严格来说,calloc 也好不到哪里去,因为空指针不能保证全为零。
  • @jamesdlin w.r.t 清理,如果contactNULL,我们不应该调用free(contact-&gt;fName);,所以我在开头勾选了if (contact == NULL) return NULL;。是的,alloc_ci()可以使用release_ci(),我现在已经修改了。
猜你喜欢
  • 1970-01-01
  • 2019-01-03
  • 1970-01-01
  • 2018-09-24
  • 1970-01-01
  • 2020-10-26
  • 1970-01-01
  • 1970-01-01
  • 2020-08-02
相关资源
最近更新 更多