动态分配和复制数组
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。编译器甚至不会生成对 malloc
和 strlen
的任何调用!确实没用。
那么这个直觉在哪里
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
- 现在
answer
和input
指向同一个字符串,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
中更花哨的了。同样,指针变量并不比 int
或 float
等其他变量更花哨。 int
存储一个整数。指针存储内存地址。这里没有魔法。
1它是 100% 等价的,除非你正在做一些非常奇特的事情,比如用外部程序读取变量 var
2malloc(0)
可以 return 一个非空指针,但在实践中它没有什么区别,因为取消引用将是未定义的行为它,并且分配零字节是一个非常没有意义的(哈哈,点)操作。
我有时会看到这样的代码:
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。编译器甚至不会生成对 malloc
和 strlen
的任何调用!确实没用。
那么这个直觉在哪里
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
- 现在
answer
和input
指向同一个字符串,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
中更花哨的了。同样,指针变量并不比 int
或 float
等其他变量更花哨。 int
存储一个整数。指针存储内存地址。这里没有魔法。
1它是 100% 等价的,除非你正在做一些非常奇特的事情,比如用外部程序读取变量 var
2malloc(0)
可以 return 一个非空指针,但在实践中它没有什么区别,因为取消引用将是未定义的行为它,并且分配零字节是一个非常没有意义的(哈哈,点)操作。