动态分配和复制数组

Dynamically allocating and copying an array

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

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 ("%s\n", copyStr("Hello world!"));
}

做我期望的事情。那到底有什么问题呢?

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

int answer;

answer = 42;
answer = 0;

即使是最粗略的观察者也会注意到第一个赋值

answer = 42;

没用。它将 42 的值放入 answer,只是在下一时刻被丢弃并替换为 0。这样那一行代码就可以彻底扔掉了。

让我们通过查看 optimised assembly code generated by a C compiler 来验证这一点。正如我们所见,answer = 42; 行对生成的机器码没有任何影响。

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

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

如果在这种情况下类比是有效的,那么我们必须得出结论,第一个赋值是无用的,可以省略。我们在 answer 中放置了一些东西(malloc 的结果),只是在片刻之后被扔掉并被其他东西取代。

当然,如果不进一步研究,我们不能说它是否适用,但我们可以通过再次查看生成的程序集来证实我们的怀疑。 And it is confirmed。编译器甚至不会生成对 mallocstrlen 的任何调用!确实没用。


那么这个直觉在哪里

It allocates the right amount of memory for the answer, and then copies the input to the answer

崩溃了?

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

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

  • input是指针变量
  • input(大概)指向一个字符串,是char
  • 的数组
  • answer是另一个指针变量
  • malloc(...) 动态分配 char 的新数组和 returns 一个指针 指向 所述数组
  • answer = malloc(...) 复制指向 answer 的指针,现在 answer 指向 malloc[=100 分配的数组=]
  • 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.

中获取的一堆不同的行

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

由于我们的数组可能是以 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 具有更好的签名和工作错误检查,我们在此处省略)。

简单来说。此代码:

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

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

foo();
var = bar();

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

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

这适用于任何功能,包括 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 的作用非常简单。它return是指向内存块的指针,如果分配失败则为 NULL 指针。2 就是这样。你用像 ptr = malloc(size) 这样的调用所做的事情绝对没有比将地址存储在指针变量 ptr 中更花哨的了。同样,指针变量并不比 intfloat 等其他变量更花哨。 int 存储一个整数。指针存储内存地址。这里没有魔法。

1它是 100% 等价的,除非你正在做一些非常奇特的事情,比如用外部程序读取变量 var 2malloc(0) 可以 return 一个非空指针,但在实践中它没有什么区别,因为取消引用将是未定义的行为它,并且分配零字节是一个非常没有意义的(哈哈,点)操作。