C:使用指针作为字符串:不可预测的行为

时间:2011-12-09 06:15:26

标签: c string memory pointers getchar

我正在编写一个C程序来查找用户输入中的最长行并打印行的长度和行本身。它成功地对字符进行了计数,但是在存储线本身时出现了不可预测的失败。也许我误解了C的记忆管理,有人可以纠正我。

编辑:后续问题:我现在明白dummy字符后面的块是未分配的,因此计算机可以使用它们打开范围,但是为什么存储一些角色还有用吗?在我提到的第二个例子中,程序将字符存储在“未分配”块中,即使它“不应该”。为什么呢?

变量:

    每次getchar() 时,
  • c都会存储在getchar()
  • i是当前行的长度(到目前为止)getchar()来自
  • 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Ôÿ"

7 个答案:

答案 0 :(得分:4)

你实际上并没有分配任何内存来写入!

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

在此之后,你只需要写入由假人留下的字符之后的内存,并写下谁知道什么。

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

例如,下面的buffer指向256字节(在大多数系统上)的内存,允许最多255个字符的字符串(因为你有空终结符(\ 0)存储在最后)。

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

编辑:这将从堆中分配内存,以后您可以通过在完成后调用free(buffer);来释放内存。另一种方法是根据Anders K的解决方案在堆栈中占用空间。

答案 1 :(得分:2)

您没有分配内存来存储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被定义为比用户输入中字符串的最大长度多一个。

答案 2 :(得分:2)

你正在砸碎你的筹码。您只为char dummy分配了1个字节。 真的应该是这样的:

char dummy [1024];

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

答案 3 :(得分:2)

是的,你说你误解了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
+---------+

答案 4 :(得分:1)

twostr指向一个角色,但是你将其视为一个缓冲区。

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

e.g。

static char dummy[512];
twostr = dummy;

答案 5 :(得分:1)

首先,您需要确保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 < twostrLen

答案 6 :(得分:1)

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

#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;
}