c-动态分配和复制数组



我有时会看到这样的代码:

char* copyStr(char* input) {
int inputLength;
char *answer;
inputLength = strlen(input);
answer = malloc(inputLength + 1);
answer = input;
return answer;
}

人们经常说这个代码不起作用,这种模式

answer = malloc(inputLength + 1);
answer = input;

毫无意义。为什么会这样?在我看来,代码还可以。它为答案分配了适当的内存,然后将输入复制到答案中。它似乎在我的测试中起作用,例如

int main()
{
printf ("%sn", copyStr("Hello world!"));
}

做我期望它做的事。那么它怎么了?

简单地说。此代码:

var = foo();
var = bar();

在所有1的情况下100%等同于此:

foo();
var = bar();

此外,如果foo()没有副作用,它100%相当于最后一行:

// foo(); 
var = bar();

这适用于ANY函数,包括malloc。如果我们暂时忘记malloc的作用,只专注于刚才所说的内容,我们可以很快意识到这段代码中注释中所写的内容:

answer = malloc(inputLength + 1);
// Here, the variable answer contains the return value from the call to malloc
answer = input;
// Here, it contains the value of input. The old value is overwritten, and
// is - unless you saved it in another variable - permanently lost.

malloc的作用非常简单。它返回一个指向内存块的指针,如果分配失败,则返回一个NULL指针2就是这样。使用ptr = malloc(size)这样的调用所做的绝对没有什么比将该地址存储在指针变量ptr中更美妙的了。指针变量也同样不比intfloat等其他变量更花哨。int存储一个整数。指针存储内存地址。这里没有魔法。

1这是100%等效的,除非你正在做一些非常有趣的事情,比如用外部程序读取变量var<2>TR

要回答这个问题,让我们先看一个稍微简单一些的代码片段。

int answer;
answer = 42;
answer = 0;

即使是最粗略的观察者也会注意到第一次任务

answer = 42;

毫无用处。它将42的值放入answer,但在下一个时刻被丢弃并替换为0。因此,这行代码可以完全丢弃。

让我们通过查看C编译器生成的优化汇编代码来验证这一点。正如我们所看到的,answer = 42;行实际上对生成的机器代码没有任何影响。

现在将其与有问题的代码进行比较

answer = malloc(inputLength + 1);
answer = input;

如果类比推理在这种情况下是有效的,那么我们必须得出结论,第一个赋值是无用的,可以省略。我们把一些东西(malloc的结果)放在answer中,但过了一会儿就被扔掉了,取而代之的是其他东西。

当然,如果没有进一步的研究,我们不能说它是否适用,但我们可以通过再次查看生成的组件来证实我们的怀疑。这一点得到了证实。编译器甚至不生成对mallocstrlen的任何调用!它们确实毫无用处。


那么这个直觉在哪里呢

它为答案分配合适的内存量,然后将输入复制到答案

分解?

问题在于指针和数组之间永远的混淆。

人们可能经常看到在C中,数组是指针,或者指针是数组,或者数组和指针是可互换的,或者其任意数量的变体。这些说法都是虚假和误导性的。指针和数组是完全不同的东西。他们经常一起工作,但这与一体相去甚远。让我们在代码示例中分解指针和数组。

  • input是指针变量
  • input(推测)指向字符串,该字符串是char的数组
  • answer是另一个指针变量
  • malloc(...)动态分配char的新数组,并返回指向所述数组的指针
  • answer = malloc(...)将该指针复制到answer,现在answer指向由malloc分配的数组
  • answer = input另一个指针(我们已经在上面看到)复制到answer
  • 现在answerinput指向同一个字符串malloc的结果被遗忘并丢弃

这就解释了为什么你的代码正在做你期望它做的事情。你没有两个完全相同的字符串"Hello world!"的副本,而是只有一个字符串和两个不同的指针。这可能看起来就像是医生的命令,但一旦我们做了一些稍微复杂的事情,它就会崩溃。例如,类似的代码

char *lineArray[MAX_LINES];
char buffer[BUF_LEN];
int i = 0;
while (i < MAX_LINES && fgets(buffer, BUF_LEN, stdin)) {
lineArray[i++] = copyStr(buffer);
}

最终stringArray的每个元素都指向同一个字符串,而不是从stdin指向一堆不同的行。

好的,现在我们已经确定answer = input复制了一个指针。但是我们想要复制一个数组,我们刚刚为它分配了空间!我们该怎么做?

由于我们的数组可能是以NUL结尾的字符串,因此我们可以使用一个标准库函数来复制以NUL开头的字符串。

strcpy(answer, input);

对于其他阵列,我们可以使用memcpy。主要的区别是我们必须向下传递数组的长度。

memcpy(answer, input, inputLength + 1);

这两种变体都适用于我们的情况,但第一种更可取,因为它重申了我们正在处理字符串。为了完整性,这里是固定的copyStr

char* copyStr(char* input) {
int inputLength;
char *answer;
inputLength = strlen(input);
answer = malloc(inputLength + 1);
strcpy(answer, input);
return answer;
}

顺便说一句,它的工作原理与非标准但广泛可用的strdup函数几乎相同(strdup具有更好的签名和工作错误检查,我们在这里省略了这些)。

相关内容

  • 没有找到相关文章

最新更新