为啥 <const char*> 和 <const char[]> 有非常不同的内存或指针行为?
Posted
技术标签:
【中文标题】为啥 <const char*> 和 <const char[]> 有非常不同的内存或指针行为?【英文标题】:Why do <const char*> and <const char[]> have very different memory or pointer behaviour?为什么 <const char*> 和 <const char[]> 有非常不同的内存或指针行为? 【发布时间】:2022-01-03 06:55:31 【问题描述】:我正在使用 C++ 进行试验,发现 const char*
和 const char[]
在下面的代码中表现得非常不同。如果我没有很好地表达这个问题,真的很抱歉,因为我不清楚代码中发生了什么。
#include <iostream>
#include <vector>
// This version uses <const char[3]> for <myStr>.
// It does not work as expected.
struct StrStruct
const char myStr[3];
;
// This program extracts all the string elements in <strStructList> and copy them to <strListCopy>
int main()
StrStruct strStruct1"ab";
StrStruct strStruct2"de";
StrStruct strStruct3"ga";
std::vector<StrStruct> strStructListstrStruct1, strStruct2, strStruct3;
std::vector<const char*> strListCopy;
for (StrStruct strStructEle : strStructList)
strListCopy.push_back(strStructEle.myStr);
std::cout << "Memory address for the string got pushed back in is "
<< &strStructEle.myStr << std::endl;
std::cout << "Memory address for the first element of the string got pushed back in is "
<< (void *) &strStructEle.myStr[0] << "\n" <<std::endl;
std::cout << "Show content of <strListCopy>:" << std::endl;
for (const char*& strEle : strListCopy)
std::cout << strEle << std::endl;
以下是它的输出:
Memory address for the string got pushed back in is [address#99]
Memory address for the first element of the string got pushed back in is [address#99]
Memory address for the string got pushed back in is [address#99]
Memory address for the first element of the string got pushed back in is [address#99]
Memory address for the string got pushed back in is [address#99]
Memory address for the first element of the string got pushed back in is [address#99]
Show content of <strListCopy>:
ga
ga
ga
但是,如果我只是简单地更改 StrStruct
的实现
来自:
// This version uses <const char[3]> for <myStr>.
// It does not work as expected.
struct StrStruct
const char myStr[3];
;
到
// This version uses <const char*> for <myStr>.
// It works as expected.
struct StrStruct
const char* myStr;
;
程序的输出变成这样:
Memory address for the string got pushed back in is [address#10]
Memory address for the first element of the string got pushed back in is [address#1]
Memory address for the string got pushed back in is [address#10]
Memory address for the first element of the string got pushed back in is [address#2]
Memory address for the string got pushed back in is [address#10]
Memory address for the first element of the string got pushed back in is [address#3]
Show content of <strListCopy>:
ab
de
ga
让我感到困惑的是:
为什么在第一个版本中所有字符串都具有相同的值?我尝试在 for each 循环中使用 const strStruct&
而不是 strStruct
来解决问题,但我不明白如何。
为什么const char*
和const char[]
的行为如此不同?我认为它们大致相同,原因如下:
const char myChars[] = "abcde";
const char* myCharsCopy = myChars;
std::cout << myChars << " vs " << myCharsCopy << std::endl;
它打印出abcde vs abcde
,你可以直接将const char[]
的值赋值给const char*
,没有任何错误。
-
为什么将
const char[]
更改为const char*
可以解决问题?
【问题讨论】:
for (StrStruct strStructEle : strStructList)
正在制作结构的临时副本。您正在存储指向这些副本的指针,看起来因为它们在循环结束时超出范围,程序会一遍又一遍地使用相同的位置,因此所有指针都指向同一个位置。使用for (StrStruct & strStructEle : strStructList)
获取对vector
中原件的引用,而不是它们的副本..
复制struct char x[8];
时复制字符串,复制struct char *x;
时复制字符串地址。
【参考方案1】:
理解其余部分所必需的基础知识:
数组和衰减
struct StrStruct
const char myStr[3];
;
包含数据
struct StrStruct
const char * myStr;
;
指向数据。
Arrays decay to pointers 但本身不是指针。
const char myChars[] = "abcde";
创建一个大小正好合适的数组(六个字符、五个字母和空终止符)来保存"abcde"
,并将字符串复制到数组中。请注意,这不必是const
。
const char* myCharsCopy = myChars;
定义一个指向char
的指针,并将数组myChars
分配给它。 myChars
自动衰减到进程中的一个指针。 myCharsCopy
不是myChars
的副本;它只保存myChars
的地址。请注意,只要myChars
是const
,myCharsCopy
就必须是const
。另请注意,您不能分配给数组,并且几乎不可能复制数组,除非您将其放在另一个数据结构中。您通常可以做的最好的事情是将数组中的内容复制到另一个数组(memcpy
或 strcpy
,具体取决于目标以及该数组是否为空字符数组)。
请注意,在许多用途中,例如函数参数const char[]
和const char*
表示相同的含义。
void func(const char a[], // accepts constant pointer to char
const char * b) // also accepts constant pointer to char
这些东西变得非常奇怪,原因(主要)在 1970 年代很有意义。今天我强烈推荐你使用像 std::vector
和 std::array
这样的库容器,而不是原始数组。
基于范围的 for 循环
除非您另有说明,否则基于范围的 for 循环对列表中的项目的副本进行操作。在正文中
for (StrStruct strStructEle : strStructList)
在循环的第一次迭代中,strStructEle
不是strStruct1
,甚至不是位于strStructList
中的strStructEle
的副本,它是第三个相同的对象。副本在正文结束时被销毁,从而释放使用的存储空间。
有
for (StrStruct & strStructEle : strStructList)
循环将对strStructList
中的项目的引用进行操作,因此不会复制。
现在你已经掌握了速度......
第 1 点
为什么在第一个版本中所有字符串都具有相同的值?我尝试在 for each 循环中使用 const strStruct& 而不是 strStruct 来解决问题,但我不明白如何。
自从
struct StrStruct
const char myStr[3];
;
包含复制StrStruct
时的数据。代码在这里复制数据结构
std::vector<StrStruct> strStructListstrStruct1, strStruct2, strStruct3;
更重要的是输出,这里
for (StrStruct strStructEle : strStructList) // strStructEle copied, so data in it is copied
strListCopy.push_back(strStructEle.myStr); //strStructEle.myStr decays to pointer,
// and pointer is stored in strListCopy
// this is not really a copy it's a pointer
// to data stored elsewhere
std::cout << "Memory address for the string got pushed back in is "
<< &strStructEle.myStr << std::endl; // print address of array
std::cout << "Memory address for the first element of the string got pushed back in is "
<< (void *) &strStructEle.myStr[0] << "\n" <<std::endl;
// prints address of the first item in the array, the same as the array
// strStructEle is destroyed here, so the stored pointer is now invalid.
// Technically anything can happen at this point
但在这种情况下,任何可能发生的事情似乎都是在循环的下一次迭代中将存储重用于strStructEle
。这就是为什么所有存储的指针看起来都是一样的。他们是一样的。它们是不同的对象,在不同的时间点都位于同一位置。所有这些对象都已过期,因此尝试查看它们是not a good idea。
const strStruct&
“修复”了这个问题,因为没有复制。每次迭代都对不同位置的不同对象进行操作,而不是对同一位置的不同对象进行操作。
第 3 点
为什么将
const char[]
更改为const char*
可以解决问题?
如果myStr
是指针而不是数组,情况就不同了
for (StrStruct strStructEle : strStructList) // strStructEle copied, so data in it is copied
// BUT! The data in it is a pointer to data
// stored elsewhere that is NOT copied
strListCopy.push_back(strStructEle.myStr); //strStructEle.myStr is a pointer and is
// directly stored in strListCopy
// this is still not a copy
std::cout << "Memory address for the string got pushed back in is "
<< &strStructEle.myStr << std::endl; // print address of pointer, not what
// it points at
std::cout << "Memory address for the first element of the string got pushed back in is "
<< (void *) &strStructEle.myStr[0] << "\n" <<std::endl;
// prints address of the first item pointed to by the pointer,
// and will be a totally different address
第 2 点
为什么 const char* 和 const char[] 行为如此不同?由于以下原因,我认为它们大致相同......
这是如上所述的数组衰减的结果。
旁白:
vector
s 在被允许直接包含(和own)他们正在收集的数据时处于最佳状态。它们处理所有的内存管理,并将数据保存在一个漂亮、易于缓存的块中。
【讨论】:
【参考方案2】:基于评论区的Yakov Galka和user4581301
在回答之前要弄清楚的几件事:
const char*
和const char[]
的区别:
概念上:
const char*
是指向const char
的指针
const char[]
是字符数组本身。
就代码而言:
const char*
存储一个内存地址,并且,它自己的内存地址与它所存储的内存地址不同。
const char[]
存储数组中第一个元素的内存地址,并且,它自己的内存地址与它存储的那个相同。 p>
const char myCharsArray[] = "abcde"; // Writing like this guarrentees you have an null terminator at the end
const char* myCharsPointer = "qwert\0";
std::cout << "The memory address for <myCharsArray> is "
<< &myCharsArray
<< std::endl;;
std::cout << "The memory address for the first element in <myCharArray> is "
<< (void *) &myCharsArray[0]
<< std::endl;
std::cout << "The memory address for <myCharsPointer> is "
<< &myCharsPointer
<< std::endl;
std::cout << "The memory address for the first element in <myCharsPointer> is "
<< (void *) &myCharsPointer[0]
<< std::endl;
它的输出是这样的:
The memory address for <myCharsArray> is [address#10]
The memory address for the first element in <myCharArray> is [address#10]
The memory address for <myCharsPointer> is [address#88]
The memory address for the first element in <myCharsPointer> is [address#99]
回答这三个问题:
问题一:
在第一个版本中,std::vector::push_back
不断添加它复制的字符数组中第一个元素的地址,这也是strStructEle.myStr
本身的地址,它永远不会改变。最后,列表是一堆值完全相同的内存地址。
通过使用const strStruct&
,使用对原始内容的引用。因此,它们唯一且真实的内存地址被复制到列表中。
问题 2:
如上所述的差异。
问题 3:
它允许传递原始字符数组的原始内存地址,而不是复制原始字符数组的内容,然后复制临时对象的内存地址。
【讨论】:
不是push_back
复制了数组,它的for (StrStruct strStructEle : strStructList)
也复制了数组并且超出了范围,释放了数组使用的空间,以便在下一次循环迭代中重用.
注意:只是为了混淆,在函数参数中,const char[]
实际上意味着const char*
;你不能按值传递数组。在 OP 将其嵌入结构的情况下,它实际上是一个数组,但是当在某些上下文中声明为数组的东西并不是真正的数组时,这种混淆是可以理解的。
@user4581301 我只是想知道人们从哪里知道这些东西。我正在尝试自学 C++,这是一段非常有趣的旅程。有什么推荐的资源吗?
Here's a curated list of books that are generally accepted as high quality by Stack Overflow users.。我不是 C++Primer 的忠实粉丝,因为第 5 版中的一些信息已经过时了 10 多年。不幸的是,第 6 版似乎被 COVID 或 2020 C++ 语言标准更新中的更改所阻止。一旦掌握了语言基础知识,请阅读有效的现代 C++。在那之后,疯狂地阅读你认为你会感兴趣并且有用的东西。以上是关于为啥 <const char*> 和 <const char[]> 有非常不同的内存或指针行为?的主要内容,如果未能解决你的问题,请参考以下文章
为啥我可以在 std::map<std::string, int> 中使用 const char* 作为键
为啥 atoi 函数不能将 const char * 转换为 int?