为啥指向 char 数组的指针必须需要 strcpy 才能为其数组分配字符而双引号分配不起作用?
Posted
技术标签:
【中文标题】为啥指向 char 数组的指针必须需要 strcpy 才能为其数组分配字符而双引号分配不起作用?【英文标题】:Why must a pointer to a char array need strcpy to assign characters to its array and double quotes assignment will not work?为什么指向 char 数组的指针必须需要 strcpy 才能为其数组分配字符而双引号分配不起作用? 【发布时间】:2010-12-07 08:36:49 【问题描述】:当你去删除指针时,第一个例子不起作用。程序要么在我添加空终止符时挂起,要么在没有它的情况下挂起:
Debug Assertion Failed Expression: _BLOCK_TYPE_IS_VALID(pHead->nBlockUse)
来自 Visual Studio 2008
//Won't work when deleting pointer:
char *at = new char [3];
at = "tw"; // <-- not sure what's going on here that strcpy does differently
at[2] = '\0'; // <-- causes program to hang
delete at;
//Works fine when deleting pointer:
char *at = new char [3];
strcpy(at,"t");
at[1] = 'w';
at[2] = '\0';
delete at;
那么当我使用双引号而不是 strcpy 时会发生什么?它们都将完美地输出字符串,并且调试器不会显示任何不同。
【问题讨论】:
我猜你的意思是delete[]
您已经回答了自己的问题。 strcpy() 将字符分配给数组。 = 分配一个新数组。调试器确实显示了一些不同的东西。 'at' 的值在一种情况下会发生变化,而在另一种情况下不会发生变化。
仅供参考,at = "tw";
然后at[2] = '\0';
是多余的。 "tw"
创建一个 已经 以 null 结尾的字符串文字。字符串"tw"
的内存看起来像[ 't' | 'w' | '\0' ]
。不仅如此,at[2] = '\0';
也会导致未定义的行为; "tw"
创建一个字符串文字,它是一个 不可写 的 只读 字符串,因此写入这个只读字符串文字将调用未定义的行为。要以这种方式实际分配某些内容,您必须执行 const char *at = "tw";
这将创建一个字符串文字并让 at
指向同一位置。
【参考方案1】:
因为char*
不是字符串。它只是一个指向某个字符的指针,约定可能有更多字符要跟随,并且在最后一个字符之后有一个'\0'
。
像"abc"
这样的C(以及C++)中的字符串文字只是一个字符数组,编译器会默默地添加'\0'
。当您将数组分配给指针时,数组会静默地将指针转换为第一个元素。结果是
at = "tw";
表示指针at
被分配了字符串文字"tw"
中第一个字符的地址。这样一来,它就会失去原来的价值。由于这是动态分配的字符数组的地址,因此您正在泄漏该数组。
当您稍后分配at
现在指向的数组中的一个字符时,您正在为字符串文字中的某个字符分配一个新值。这会调用未定义的行为,并且程序立即挂起或崩溃可能是您执行此操作时可能发生的最好情况。 (在许多平台上,您正在这样做写入只读内存。)
稍后您将at
传递给delete[]
(和not delete
, since you called new[]
, not new
)。这样做时,您将字符串文字的地址传递给它,而不是分配的字符数组。当然,这会弄乱堆管理器。 (VC 的运行时库在 Debug 模式下捕捉到这一点。)
另一方面,std::strcpy
将字符串从一个数组逐个字符复制到另一个数组。不会更改指针,只会复制内存片段。之后指向目标数组的指针仍然指向目标数组,只是该数组中的数据发生了变化。
让我补充一下:作为 C++ 的初学者,您应该使用 std::string
,而不是 C 字符串。这为您完成了所有繁琐的工作并且具有合理的语义。
【讨论】:
【参考方案2】:你弄错了两件事:让指针指向不同的东西(这就是赋值所做的)并将一些数据复制到指针指向的位置。
at = "tw";
此代码使at
指向在只读内存中某处创建的文字“tw”。尝试写入它是一种未定义的行为。
char *at = new char [3];
strcpy(at,"t");
这段代码为三个字符分配内存并使at
指向这部分内存(第1行),然后将一些数据复制到at
指向的内存中。
请记住,使用new[]
分配的内存应该使用delete[]
释放,而不是delete
我建议您了解有关指针的更多信息。 This discussion 涵盖了这个。
【讨论】:
【参考方案3】:当你这样做时
char *at = ...;
at = "hello";
您基本上是用静态常量字符串的地址覆盖指针值(即new[]
为您分配的内存地址)。这意味着当您稍后删除 该 内存时,您将传递 delete
一个以前未由 new
返回的指针。
这是一件坏事。
在 C 和 C++ 中,对指针的赋值通常不会对指向的内存做任何事情,它们会改变指针本身。如果您习惯于字符串更多是“一等公民”的语言,这可能会让人感到困惑。
此外,如果您使用了new[]
,则应使用delete[]
。
【讨论】:
那么假设 strcpy(var,"string") 循环遍历“string”中的每个单独字符并将其分配给 var 中的正确索引是否正确? @Omar:是的,strcpy() 将一次写入一个字符,直到并包括终止 NIL 字符。【参考方案4】:需要了解 3 件事:
1) char *at;
只是一个指针变量。
指针变量仅仅意味着它拥有一个内存地址。
2)new char[3]
返回堆上分配内存的起始地址。
3) "hello"
返回字符串字面量的地址。
char *at = new char [3];
//at now contains the address of the memory allocated on the heap
at = "hello";
//at now contains the address of the static string.
// (and by the way you just created a 3 byte memory leak)
delete[] at;
//WOOPS!!!! you can't do that because you aren't deleting
// the original 3 chars anymore which were allocated on the heap!
//Since at contains the string literal's memory address you're
// trying to delete the string literal.
关于修改只读内存的注意事项:
你也不应该修改字符串文字。 IE。永远不要这样做:
char *at = "hello";
at[2] = '\0';
字符串文字的内存必须是只读的,如果你改变它,C++ 语言未定义结果。
由于您使用的是 C++:
由于您使用的是 C++,请考虑改用 std::string
类型。
#include <string>
using namespace std;
int main(int argc, char **argv)
string s = "hello";
s += " world!";
//s now contains "hello world!"
s = "goodbye!";
//Everything is still valid, and s contains "goodbye!"
//No need to cleanup s.
return 0;
【讨论】:
【参考方案5】:在第一个示例中,您导致了内存泄漏。
您的变量at
是指向内存地址的指针,而不是字符串本身。当您将"tw"
的地址分配给指针时,您丢失了使用new
获得的原始地址。 at
现在指向一个你没有用new
分配的地址,所以你不能delete
它。
如果您将指针视为整数,它可能更有意义。为了便于讨论,我指定了任意数字作为地址。
char *at = new char[3]; // 0x1000
at = "tw"; // 0x2000
at[2] = '\0'; // set char at 0x2002 to 0
delete at; // delete 0x2000 (whoops, didn't allocate that!)
【讨论】:
【参考方案6】:在您的第一个示例中,您分配了一些内存并使用“at”变量指向它。当你这样做
at = "tw"
您实际上是在将 char * 重新指向一个常量字符串。这会导致您泄漏内存。当您继续删除“at”时,您正在尝试删除堆栈内存。
strcpy 遍历每个字符并将它们的值复制到您分配的新内存中。这也称为深拷贝。
【讨论】:
【参考方案7】:一个指针保存一个地址。指针的 = 运算符更改所保存的地址。
at = "tw";
指向数组“tw”(由编译器创建的用于保存字符 tw 的数组),它不再指向您使用 new 创建的数组。在文件中创建。
at[2] = '\0';
在编译器数组的末尾添加一个 NULL。
【讨论】:
"at[2] = '\0';
在编译器数组的末尾添加一个 NULL"-这是调用 UB 作为字符串文字(您所指的编译器创建的数组)是只读的并且永远不应该被修改。但我假设你知道这一点;我只是想为其他阅读您的答案的人指出这一点。【参考方案8】:
别忘了使用
delete []
每当你用 [] 分配东西时。
【讨论】:
【参考方案9】:在第一个示例中,您正在更改 at 的值,在第二个示例中,您正在更改 at 指向的值。将 char * 分配给双引号字符串会将其分配给静态 const 指针。
特别是,在第一个示例中,现在指向内存中的不同位置。
【讨论】:
在示例const char *s = "hello world;
中,s
是指向 const char 的指针,而不是指向 char 的 const 指针。您的答案中有很多错别字...另外,我假设您可能想说“将双引号字符串分配给 char *
而不是相反?因为您不能将任何内容分配给字符串文字,即字符串文字永远不能在 LHS 上?以上是关于为啥指向 char 数组的指针必须需要 strcpy 才能为其数组分配字符而双引号分配不起作用?的主要内容,如果未能解决你的问题,请参考以下文章
在 C# String 构造函数 String(Char*) 中,为啥构造函数不期望指向字符数组的指针?
为啥字符串函数有一些参数作为 const char *(指向常量字符的指针)?
指向 char 指针数组的指针与指向 char 指针的指针(或 char** argv 与 char* (*argv)[])