【问题标题】:Segfault when using scanf()使用 scanf() 时的段错误
【发布时间】:2016-07-16 12:01:32
【问题描述】:

我是 C 的初学者。这是一个简单的程序,它提示输入键盘输入的名称,使用该名称创建一个问候语,然后将其打印出来。在运行时,在控制台输入名称并按回车后立即发生分段错误。调试后,我怀疑问题出在 scanf() 函数上。我尝试使用“*”和“&”调整参数“name”,并使用空字符串初始化 char 数组“name”,但这些都没有帮助。

// Prompt for a name and print a greeting using the name.    

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

int main()
{
    // Prompt for a name.
    printf("What is your name? ");

    // Get the name.
    char name[20];
    scanf("%s", name);    // Suspect segfault occurs here...

    // Construct the greeting.
    char *greeting;
    char *suffix;
    greeting = "Hello, ";
    suffix = ", nice to meet you!";
    strcat(greeting, name);
    strcat(greeting, suffix);

    // Display the greeting.
    printf("%s", greeting);

    return 0;
}

【问题讨论】:

  • Suspect segfault occurs here... 我以为你说你会运行调试器?这将清楚地表明段错误发生的位置,而不存在。如前所述,这是因为您正在尝试使用常量数据(即 UB)来做事。无论如何,当您可以简单地执行const char c[] = "stuff"; 时,无需执行char *c; c = "stuff"; 的两个步骤 - 这使得您在此处处理只读内存的概念更加清晰。
  • @underscore_d:char *c; c = "stuff"; 的捷径是char * c = "stuff";。你的提议显着改变了局面。
  • @alk 当然,这更字面意思。如果由于 C++ 更适合我而忘记了一些基本的东西,我深表歉意 - 有什么重大变化?考虑到 C 急切地将数组名称转换为指向其第一个元素的指针,名称c 在大多数情况下将具有相同的语义。但如果有的话,它似乎更好,因为您可以使用它来获取sizeof 字符串(包括空终止符)。
  • @underscore_d:哦,是的,很公平,我忽略了const。然而,与 C++ 相比,这在 C 中并不是什么大问题。仍然指针不是数组。
  • 好吧,好吧,我的评论“不过在 C 中这不是什么大问题”是废话,因为写入 const 变量会引发 UB。

标签: c segmentation-fault scanf


【解决方案1】:

两次调用strcat()

char *greeting;
char *suffix;
greeting = "Hello, ";
suffix = ", nice to meet you!";
strcat(greeting, name);
strcat(greeting, suffix);

通过尝试附加到属于“字符串”字面量 ("Hello, ") 的存储来引发未定义的行为。

一个“字符串”-字面量的存储是

  1. 常数
  2. 即使没有,它也不会提供任何额外的空间来添加任何内容。

要解决此问题,请提供足够大的缓冲区并将所需的所有内容复制到那里:

char *greeting;
char *suffix;
greeting = "Hello, ";
suffix = ", nice to meet you!";
char buffer[7 + 20 + 19 + 1]; /* 7 for "Hello, ", 
                                20 for name (which in fact for your code needed to be 19 only),
                                19 for ", nice to meet you!" and
                                 1 for the 0-terminator. */
strcpy(buffer, greeting); /* Use strcpy() to copy to an uninitialised buffer. */ 
strcat(buffer, name);
strcat(buffer, suffix);

还要确保用户不会溢出为name 提供的内存告诉scanf() 有多少可用;

  char name[20 + 1]; /* If you need 20 characters, define 1 more to hold the
                        "string"'s 0-terminator. */

  scanf("%20s", name); /* Tell scanf() to read in a maximum of 20 chars. */

【讨论】:

    【解决方案2】:

    问题出在

    strcat(greeting, name);
    

    greeting 指向read-only 内存。将name 附加到greeting 会尝试更改其中的内容。结果是段错误。

    【讨论】:

      【解决方案3】:
         strcat(greeting, name);
      

      strcat 的调用正在修改一个字符串常量——这是不合法的,这就是导致您的段错误的原因(从技术上讲,您看到的是未定义行为的结果)。 p>

      为了完整性:

      scanf("%s", name);
      

      如果缓冲区的大小限制为 20,那么您应该使用:

      scanf("%19s", name);
      

      ... 限制实际存储的字符数(尽管有更好的方法来读取可变长度行)。我使用 19 是因为需要为 nul 终止符留出空间。

      然后,为您的完整字符串分配合适的存储空间:

      char full_greeting[20 + 7 + 19] = ""; // name + "hello"... + "nice to meet"...
      

      然后复制进去:

      strcpy(full_greeting, greeting);
      strcat(full_greeting, name);
      strcat(full_greeting, suffix);
      
      printf("%s", full_greeting);
      

      动态字符串解决方案(POSIX)

      在 POSIX 系统上,您可以让 scanf 为其读取的名称分配一个缓冲区:

      char *name = NULL;
      scanf("%ms", &name); // you could also use 'getline' function
      if (name == NULL) {
          exit(1); // scanf failed or memory allocation failed
      }
      

      (请注意,使用getline 会读取一整行,这与当前的scanf 不同,后者读取一个字符串直到第一个空格)。

      然后,动态计算缓冲区的长度:

      int req_len = strlen(name) + strlen(greeting) + strlen(suffix) + 1;
      // (+1 is for nul terminator)
      char * buffer = malloc(req_len);
      if (buffer == NULL) {
          exit(1); // or handle the error somehow
      }
      strcpy(buffer, greeting);
      strcat(buffer, name);
      strcat(buffer, suffix);
      
      printf("%s", buffer);
      free(buffer);
      free(name);
      

      【讨论】:

      • 20s 应该是 19s - 空字符需要空格
      • @EdHeal 是的,已修复。谢谢。
      【解决方案4】:
      char *greeting;
      char *suffix;
      greeting = "Hello, ";
      suffix = ", nice to meet you!";
      strcat(greeting, name);
      strcat(greeting, suffix);
      

      当目的地不仅不可修改而且太小时,您正在制作strcat。你可能知道写作

      char *p = "hello";
      *p = 'x';
      

      是未定义的行为。这就是 strcat 对您传入的 greeting 参数所做的事情。解决方案是

      #define MAXBUF 64
      
      char *mystrcat(char *dest, char *src)
      {
          while (*dest) 
              dest++;
          while (*dest++ = *src++)
              ;
      
          return --dest;
      }
      
      char greeting[MAXBUF], *p;
      
      strcpy(greeting, "hello, ");
      p = mystrcat(greeting, name);
      mystrcat(p, ", nice to meet you");
      

      另外,请注意新函数mystrcat。这是Joel Spolsky's famous post 中解释的优化。

      【讨论】:

      • "char *p = "hello"; *p = 'x'; 是未定义的行为。这就是 strcat 对问候参数所做的..." 不完全正确。或者“这个”指的是什么?
      • 下面是修改字符串。当然,这不仅仅是用x 替换第一个字符。 @alk "This" 指的是相同的类型动作
      • 详细说明“目的地不仅是不可修改的……”。目的地greeting可能是可修改的。可能不会。正在尝试修改的是 UB。
      • @chux 这似乎混淆了可修改的 2 个可能定义:对象是否被声明为 const 与操作系统是否已将其放入内存的只读页面。显然,我们不能依赖后者,这样做就是 UB,所以唯一重要的指标是前者;从这个意义上说,说它不可修改是准确的,如果不是完全准确的话。
      猜你喜欢
      • 2020-10-26
      • 2012-11-07
      • 1970-01-01
      • 2013-12-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多