【问题标题】:C: using pointer as string: unpredictable behaviorC:使用指针作为字符串:不可预测的行为
【发布时间】:2011-12-09 06:15:25
【问题描述】:

我正在编写一个 C 程序来查找用户输入中最长的行并打印行的长度和行本身。它成功地计算了字符数,但意外地无法存储行本身。也许我误解了 C 的内存管理,有人可以纠正我。

编辑:后续问题:我现在明白dummy 字符后面的块是未分配的,因此计算机可以使用它们做任何事情,但是为什么存储一些字符仍然有效吗?在我提到的第二个示例中,程序将字符存储在“未分配”块中,即使它“不应该”。为什么?

变量:

  • getchar() 存储在 c 每次我 getchar()
  • i 是我 getchar()ing 来自的当前行的长度(到目前为止)
  • longest_i 是目前为止最长的行的长度
  • twostr 指向两个字符串中第一个字符串的开头:第一个表示当前行,第二个表示迄今为止最长的行。当发现一行最长时,将其复制到第二个字符串中。如果未来的行更长,它会覆盖第二个字符串的某些部分,但这没关系,因为我不会再使用它了——第二个字符串现在将从更靠右的位置开始。
  • dummytwostr 一个指向的地方

这就是我如何可视化程序变量使用的内存:

 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|\n| 7|11|15|c |u |r |r |e |n |t |\0|e |s |t |\0|p |r |e |v |l |o |n |g |e |s |t |\0|
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

真实陈述:

&c == 11
&i == 12
&longest_i == 13
&twostr = 14
&dummy = 15

程序:

#include <stdio.h>

int main()
{
    char c = '\0';
    int i, longest_i;
    char *twostr;
    longest_i = i = 0;
    char dummy = '\0';
    twostr = &dummy;

    while ((c=getchar()) != EOF)
    {
        if (c != '\n')
        {
            *(twostr+i) = c;
            i++;
        }
        else
        {
            *(twostr+i) = '\0';
            if (i > longest_i)
            {
                longest_i = i;
                for (i=0; (c=*(twostr+i)) != '\0'; ++i)
                    *(twostr+longest_i+1+i) = c;
            }
            i = 0;
        }
    }

    printf("length is %d\n", longest_i);
    for (i=0; (c=*(twostr+longest_i+1+i)) != '\0'; ++i)
        putchar(c);

    return 0;
}

*(twostr+longest_i+1))'\0' 是不可预测的。例子:

输入:

longer line
line

输出:

length is 11
@

输入:

this is a line
this is a longer line
shorter line

输出:

length is 21
this is a longer lineÔÿ"

【问题讨论】:

  • 从 twostr 复制到 twostr+longest_i 时,您不会复制终止的 null。有关详细信息,请参阅下面的答案。

标签: c string memory pointers getchar


【解决方案1】:

您实际上并没有分配任何要写入的内存!

char dummy = '\0'; // creates a char variable and puts \0 into it
twostr = &dummy; // sets twostr to point to the address of dummy

在此之后,您只需写入由 dummy 保留的字符之后的内存,然后写入 who-knows-what。

在这种情况下,最简单的解决方法是使 dummy 成为一个指向 char 的指针,然后 malloc 一个缓冲区以用于您的字符串(使其比您期望的最长字符串长!)

例如,下面的buffer 将指向 256 字节(在大多数系统上)的内存,允许最长为 255 个字符的字符串(因为您在末尾存储了空终止符 (\0))。

char * buffer = (char *)malloc(sizeof(char) * 256);

编辑:这将从堆中分配内存,稍后您应该在完成后通过调用free(buffer); 来释放它。另一种方法是按照 Anders K 的解决方案用完堆栈上的空间。

【讨论】:

  • 好电话,我最近大部分时间都在用 C++ 编写 C 风格的代码 :)
  • 我不认为 malloc 对于 C++ 是安全的。一般来说,据我所知,最好使用 new 和 delete 。但很高兴知道这些年来我一直在徒劳地输入 malloc 返回值。
【解决方案2】:

你正在破坏你的堆栈。您只有 1 个字节分配给 char dummy。 真的应该是这样的:

char dummy[1024];

您还需要确保写入的字节数不超过 1024 或 1023 个字节以允许空终止符。

【讨论】:

    【解决方案3】:

    您没有分配内存来存储getchar 读取的字符。您的指针twostr 是指向字符变量而不是数组的字符指针,但您将其视为指向 char 数组的指针:

    char *twostr;
    ....
    char dummy = '\0';
    twostr = &dummy;
    ....
    *(twostr+i) = c;  // when i here is > 0 you are accessing invalid memory.
    

    你需要的是这样的:

    char *twostr = malloc(MAX);
    // use it.
    free(twostr);
    

    其中MAX 被定义为比用户输入中字符串的最大长度大一。

    【讨论】:

      【解决方案4】:

      是的,你说得对,你误解了 C 的内存管理模型。

      排队

      *(twostr+i) = c;
      

      例如,这是正确的,除了twostr 包含一个字符的地址并且只有*twostr 指向您拥有的内存。添加除0 之外的任何内容以获取另一个地址并取消引用会产生未定义的行为,因为属于dummy 的内存大小为1 个字节。

      长话短说,您需要分配一块内存来存储字符串。最简单的方法是向您展示如何正确操作,所以这里是经过更正的代码:

      #include <stdio.h>
      
      int main()
      {
          char c;
          int i, longest_i;
          char twostr[1024]; // twostr points to a block of memory 1024 bytes long
          char longest[1024]; // so does longest, where we will store the longest string
      
          longest_i = i = 0;
          char dummy = '\0';
      
          while ((c=getchar()) != EOF && i < 1024) // we check that i < 1024 so we don't
                                                   // go outside the bounds of our arrays
          {
              if (c != '\n')
              {
                  *(twostr+i) = c;
                  i++;
              }
              else
              {
                  twostr[i] = 0;
                  if (i > longest_i)
                  {
                      longest_i = i;
                      for (i = 0; twostr[i] != 0; ++i) { // 0 is the same as '\0'
                          longest[i] = twostr[i];
                          twostr[i] = 0; // fill twostr with NULLs
                      }
                  }
                  i = 0;
              }
          }
      
          printf("length is %d\n", longest_i);
          for (i=0; longest[i] != 0; ++i)
              putchar(longest[i]);
      
          return 0;
      }
      

      此外,您可视化程序变量的方式是不正确的。真的是这样的:

      堆栈:

      +---------+
      |    c    |   1 byte
      +---------+
      |         |
      |         |
      |         |
      |    i    |   4 bytes
      +---------+
      |         |
      |         |
      |         |
      |longest_i|   4 bytes
      +---------+
      |         |
      |         |
      |         |
      
      ~~~~~~~~~~~
      
      |         |
      |         |
      |  twostr |   1024 bytes
      +---------+
      |         |
      |         |
      |         |
      
      ~~~~~~~~~~~
      
      |         |
      |         |
      | longest |   1024 bytes
      +---------+
      

      【讨论】:

        【解决方案5】:

        首先,您需要确保 twostr 有足够的空间来保存您正在管理的字符串。您可能需要添加一些额外的逻辑来分配初始空间以及在需要时分配额外的空间。比如:

        size_t twostrLen = 256;
        char* twostr = malloc(twostrLen);
        

        然后将数据插入其中,如果您的索引将超过 twostrLen 的当前长度,您需要确保分配额外的内存:

        if (i >= twostrLen) {
           char* tmp = twostr;
           twostrLen *= 2;
           twostr = malloc(twostrLen);
           memcpy(twostr, tmp, i-1);
           free(tmp);
        }
        

        其中i 是您将要写入的与twostr 的偏移量。

        最后,当从当前字符串复制到最长字符串时,你的循环终止条件是c=*(twostr+i)) != '\0'。这将在c 匹配'\0' 时触发,退出循环写入终止空值。您需要确保写入 null 以便循环打印字符串能够正常工作。在最里面的 for 循环之后添加以下内容应该可以解决问题:

        *(twostr+longest_i+1+i) = 0;
        

        没有这个,我们的最后一个循环将继续读取,直到遇到空字符。这可能是立即的(如您的第一个示例中所见,它似乎可以工作),也可能是稍后的一些字节(如您的第二个示例,其中打印了额外的字符)。

        再次提醒,在写入该位置之前,请记住检查 longest_i+1+i &lt; twostrLen

        【讨论】:

        • 你发现了这个错误。我在复制到第二个字符串的循环之后添加了*(twostr+longest_i+1+i) = '\0';,它运行良好。
        【解决方案6】:

        twostr 指向一个字符,但是您将其视为缓冲区。

        你需要做的是做一个缓冲区,而不是可以容纳更多的字符

        例如

        static char dummy[512];
        twostr = dummy;
        

        【讨论】:

        • 是的,但是如果最长的字符串超过 512 个字节怎么办?
        【解决方案7】:

        试试下面的代码。希望你能得到预期的结果:

        #include <stdio.h>
        
        #define LENGTH 1024
        
        int main()
        {
            char c;
            int i, longest_i;
            char twostr[LENGTH]=""; // twostr points to a block of memory 1024 bytes long
            char longest[LENGTH]=""; // so does longest, where we will store the longest string
        longest_i = i = 0;
        char dummy = '\0';
        
        while ((c=getchar()) != EOF && i < LENGTH) // we check that i < 1024 so we don't
                                                 // go outside the bounds of our arrays
        {
            if (c != '\n')
            {
                *(twostr+i) = c;
                i++;
            }
            else
            {
                twostr[i] = 0;
                if (i > longest_i)
                {
                    longest_i = i;
                    for (i = 0; twostr[i] != 0; ++i) { // 0 is the same as '\0'
                        longest[i] = twostr[i];
                        twostr[i] = 0; // fill twostr with NULLs
                    }
                }
                i = 0;
            }
        }
        
        printf("length is: %d\n", longest_i);
        printf("And the word is: ");
        puts(longest);
        printf("\n");
        return 0;
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-06-20
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多