【问题标题】:using strtol on a string literal causing segmentation fault在导致分段错误的字符串文字上使用 strtol
【发布时间】:2018-11-02 10:28:28
【问题描述】:

我有一个通过getline() 得到的字符串(更准确地说,我使用循环和getline 逐行读取文件)

假设这条线是12|34|

然后我使用 strtok() 将其减少substr = strtok(line, "|"); 并将它们存储到带有循环的字符串数组中,part[index] = substr;

所以这里的part[0]应该是“12”,part[0]应该是“34” 我想使用 strtol,但我检查了它不能用于字符串文字,然后我尝试以下代码。

char *temp = strdup(part[1]);
char **ptr;
long ret = strtol(temp, ptr, 10);
printf("%x\n", ret);

当我阅读第二行时,它会导致分段错误。通过我如何才能真正使用strtol 将字符串转换为整数

【问题讨论】:

  • 旁注:你从来没有在字符串文字上调用strtol,所以我不确定你的标题来自哪里。您最初认为不能在字符串文字上调用 strtol 的论点是错误的。
  • 虽然所有关于 ptr 和 strtol 错误使用的观察都是正确的,但我怀疑这会导致分段错误。您可以发布更完整的代码版本吗?特别是 part[] 的定义?
  • @GermanNerd 如果您将非空指针传递给strtol,它将尝试在此地址写入,所以是的,很可能会出现段错误(好吧,如果您的系统有 MMU)
  • @Jean-François Fabre OP 的代码声明了一个指向 char* 的指针。在未初始化时,它被分配在堆栈上,如果编译器接受从指针到 char* 到 char* 的隐式转换(就像 gcc 所做的那样),strtol() 可以写入它。只有在取消引用未初始化的指针时,您才能创建段错误。
  • 是的,**ptr 指针已分配,但strtol 取消引用它(读取它的值)以在那里写一些东西(它是指针上的指针)。所以这是未定义的行为。 ptr 的值对于指针无效。当然&ptr 已初始化,但这不是重点。*

标签: c linux strtok strtol


【解决方案1】:

问题是ptr 没有初始化。因此,当strtol 尝试写入地址ptr 时,它会崩溃(或未定义的行为)。

你必须传递一个指针的有效地址来存储最后一个未处理的字符,比如:

char *ptr;
long ret = strtol(temp, &ptr, 10);

&ptr有效,指向ptr的自动变量存储位置

【讨论】:

    【解决方案2】:

    您误用了strtol。它需要一个char**,因为它打算根据手册页设置它指向的char*

    如果endptr不是NULLstrtol()将第一个无效字符的地址存储在*endptr中。

    通过将未初始化的char** 传递给它,当它试图取消引用它时,您会调用未定义的行为。将代码更改为:

    char *ptr;  // Place to put the end ptr
    long ret = strtol(temp, &ptr, 10);  // Pass address of the location it can set
    

    或者,如果你从不使用ptr,那么就这样做:

    long ret = strtol(temp, NULL, 10);  // Don't care about end ptr; strtol won't set on NULL
    

    【讨论】:

      【解决方案3】:
      char **ptr;
      long ret = strtol(temp, ptr, 10);
      

      错了。 ptr 没有初始化,也没有引用任何有用的东西。

      strtol() 的第二个参数必须引用一个实际的char * 值的地址,该值存储第一个未转换字符的地址。每7.22.1.3 The strtod, strtof, and strtold functions of the C standard

      指向最终字符串的指针存储在指向的对象中 endptr,前提是 endptr 不是空指针。

      正确的代码是

      char *endptr;
      long ret = strtol(temp, &endptr, 10);
      

      char *endptr;
      char **ptr = &endptr;
      long ret = strtol(temp, ptr, 10);
      

      在这种情况下,在调用 strtol() 之后,endptr 中的值将是没有转换为结果 long 值的第一个字符的地址。

      如果您不关心第一个未转换的字符是什么:

      char **ptr;
      long ret = strtol(temp, NULL, 10);
      

      【讨论】:

        【解决方案4】:

        函数strtol(const char *str, char **str_end, int base); 将取消对str_end 的引用,并将执行类似*str_end = one_after_end_of_parsed_long 的操作;因此,当您传递 char** 类型的指针时,该指针未指向可被 strtol 修改的有效指针对象,那么您将产生未定义的行为。

        你宁愿写

        char *ptr;  // space for taking on a pointer value
        long ret = strtol(temp, &ptr, 10);
        

        或(不是首选变体):

        char **ptr = malloc(sizeof(char*));
        long ret = strtol(temp, ptr, 10);
        ...
        free(*ptr);
        

        【讨论】:

          【解决方案5】:

          在这里你已经分离了你的字符串。 所以每个字符串都包含一个长数字。第二个参数用于了解转换在字符串中停止的位置。 如果不需要,请传入NULL

          char *temp = strdup(part[1]);
          long ret = strtol(temp, NULL, 10);
          printf("%lx\n", ret);
          

          另外printf对于长号需要不同的格式标志。这里lx 代表long hexadecimal

          【讨论】:

          • 第二个参数不限制转换的大小,它报告它在哪里结束。大不同。
          • 是的,我显然不知道如何阅读一个人。我的坏,更新了。
          【解决方案6】:

          完全没有必要使用strtok(),因为strtol()将第二个参数指向的指针设置为指向解析后的数字后面的字符。

          一个完整的示例程序:

          #define  _POSIX_C_SOURCE  200809L
          #include <stdlib.h>
          #include <stdio.h>
          #include <errno.h>
          
          int main(void)
          {
              char    *line_ptr = NULL;
              size_t   line_max = 0;
              ssize_t  line_len;
          
              long    *number      = NULL;
              size_t   numbers     = 0;
              size_t   numbers_max = 0;
          
              char    *curr, *next, *ends;
              long     temp;
              size_t   i;
          
              while (1) {
          
                  line_len = getline(&line_ptr, &line_max, stdin);
                  if (line_len < 1)
                      break;
          
                  curr = line_ptr;
                  ends = line_ptr + line_len;
          
                  numbers = 0;
                  while (1) {
          
                      /* Parse next long. */
                      next = curr;
                      errno = 0;
                      temp = strtol(curr, &next, 0);
                      if (errno)
                          break;
                      if (next == curr)
                          break;
          
                      /* Need to grow number array first? */
                      if (numbers >= numbers_max) {
                          size_t  temp_max = (numbers | 1023) + 1025 - 16;
                          long   *temp_ptr;
          
                          temp_ptr = realloc(number, temp_max * sizeof number[0]);
                          if (!temp_ptr) {
                              fprintf(stderr, "Out of memory.\n");
                              exit(EXIT_FAILURE);
                          }
          
                          numbers_max = temp_max;
                          number      = temp_ptr;
                      }
          
                      /* Save parsed number. */
                      number[numbers++] = temp;
          
                      /* Skip trailing whitespace, */
                      curr = next;
                      while (curr < ends && (*curr == '\t' || *curr == '\n' || *curr == '\v' ||
                                             *curr == '\f' || *curr == '\r' || *curr == ' '))
                          curr++;
          
                      /* Skip separator. */
                      if (*curr == '|')
                          curr++;
                      else
                          break; /* No separator, so that was the final number. */
                  }
          
                  printf("Parsed %zu longs:", numbers);
                  for (i = 0; i < numbers; i++)
                      printf(" %ld", number[i]);
                  printf("\n");
                  fflush(stdout);
              }
          
              if (ferror(in)) {
                  fprintf(stderr, "Error reading standard input.\n");
                  exit(EXIT_FAILURE);
              }
          
              free(line_ptr);
              line_ptr = NULL;
              line_max = 0;
          
              free(number);
              number = NULL;
              numbers = 0;
              numbers_max = 0;
          
              return EXIT_SUCCESS;
          }
          

          除了可用内存之外,这个程序没有任何限制。行长或它存储在数组中的数字数量。数字数组的增长策略很时髦(只是我的风格);随意用您喜欢的任何东西替换它。只要确保temp_max 至少是numbers + 1。使其更大意味着您一次分配更多,因此执行更少的“慢” realloc() 调用。

          外部while 循环遍历从标准输入读取的行。

          内部 while 循环解析该行中的 long,由管道字符 | 分隔。 strtol() 忽略前导空格。如果数字和后面的管道字符之间有空格,我们需要明确地跳过它;你也可以只使用while (curr &lt; ends &amp;&amp; isspace(*curr)) curr++;

          如果您想将所有 long 收集到单个数组中,而不是每行,只需在内部 while 循环之前省略 numbers = 0;。 (并在外部 while 循环之后打印出数字。)

          实际转化,

          next = curr;
          errno = 0;
          temp = strtol(curr, &next, 0);
          if (errno)
              break; /* errno == ERANGE; number too large in magnitude! */
          if (next == curr)
              break; /* end of input, no number */
          

          依赖于如果要转换的数字量级太大,strtol() 将设置errno = ERANGE 并返回LONG_MIN(如果字符串中的数字为负数)或LONG_MAX(如果为正数) )。为了检测到这一点,我们必须首先将errno 设置为零。如果字符串为空(或行中有一个杂散的 nul 字符 \0),strtol() 将返回 0 和 next == curr

          【讨论】:

            猜你喜欢
            • 2020-04-23
            • 2020-03-20
            • 2013-05-26
            • 2017-08-02
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2019-09-25
            相关资源
            最近更新 更多