为什么<const char*>和<const char[]>具有非常不同的内存或指针行为?

Why do <const char*> and <const char[]> have very different memory or pointer behaviour?

本文关键字:char gt const lt 内存 指针 为什么 非常      更新时间:2024-05-10

我正在试验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> strStructList{strStruct1, 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

让我感到困惑的是以下几点:

  1. 为什么在第一个版本中所有字符串都具有相同的值?我试图在每个循环中使用const strStruct&而不是strStruct来解决问题,但我不明白如何。

  2. 为什么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*,而不会出错。

  1. 为什么将const char[]更改为const char*可以解决问题?

理解其余部分所需的基础知识:

数组和衰减

struct StrStruct
{
const char myStr[3];
};

包含数据

struct StrStruct
{
const char * myStr;
};

指向数据。

数组衰减到指针,但不是指针本身。

const char myChars[] = "abcde";

创建一个大小完全正确的数组(六个字符、五个字母和 null 终止符)来保存"abcde"并将字符串复制到数组中。请注意,这不需要const

const char* myCharsCopy = myChars;

定义指向char的指针,并为其分配数组myChars。 在此过程中,myChars会自动衰减到指针。myCharsCopy不是myChars的副本;它只保存myChars的地址。注意,只要myCharsconstmyCharsCopy就必须const。另请注意,您无法分配给数组,并且几乎不可能复制数组,除非您将其放置在另一个数据结构中。您通常能做的最好的事情是将数组中的内容复制到另一个数组(memcpystrcpy取决于目标以及数组是否为以 null 结尾的字符数组)。

请注意,在许多用法中,例如函数参数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::vectorstd::array这样的库容器,而不是原始数组。

基于范围的循环

基于范围的 for 循环对列表中项的副本进行操作,除非您另行指定。在体内

for (StrStruct strStructEle : strStructList)

在循环的第一次迭代中,strStructEle不是strStruct1,甚至不是位于strStructList中的strStructEle的副本,它是第三个相同的对象。副本在正文末端被销毁,释放使用的存储空间。

for (StrStruct & strStructEle : strStructList)

循环将对strStructList中的项的引用进行操作,因此不会创建副本。

现在您已经掌握了速度...

要点 1

为什么在第一个版本中所有字符串都具有相同的值?我试图在每个循环中使用const strStruct&而不是strStruct来解决问题,但我不明白如何。

因为

struct StrStruct
{
const char myStr[3];
};

包含创建StrStruct副本时的数据。代码在此处复制数据结构

std::vector<StrStruct> strStructList{strStruct1, 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。这就是为什么所有存储的指针看起来都相同的原因。它们是一样的。它们是不同的对象,它们都驻留在不同时间点的同一位置。所有这些对象都已过期,因此尝试查看它们不是一个好主意。

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

为什么常量字符

*和常量字符[]的行为如此不同?由于以下原因,我认为它们大致相同...

如上所述,这是阵列衰减的结果。

题外话:

vector被允许直接包含(并拥有)他们正在收集的数据时,它们处于最佳状态。它们处理所有的内存管理,并将数据保存在一个漂亮的、易于缓存的块中。

基于 Yakov Galka 和评论部分中的 user4581301

在回答之前要清除的几件事:

const char*const char[]的区别:

从概念上讲

const char*是指向const char的指针

const char[]是字符数组本身。

在代码方面

const char*存储内存地址,并且它自己的内存地址与它存储的内存地址不同

const char[]存储数组中第一个元素的内存地址,并且它自己的内存地址与它存储的内存地址相同

const char myCharsArray[] = "abcde";      // Writing like this guarrentees you have an null terminator at the end   
const char* myCharsPointer = "qwert";                                                                                
                                                     
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]

要回答这三个问题:

问题1:

在第一个版本中,std::vector::push_back不断在其复制的字符数组中添加第一个元素的地址,这也是strStructEle.myStr本身永远不会更改的地址。最后,列表是一堆内存地址,其值完全相同。

通过使用const strStruct&,使用对原始内容的引用。因此,他们唯一和真实的内存地址被复制到列表中。

问题2:

如上所述的差异。

问题3:

它允许传递原始字符数组的原始内存地址,而不是复制原始字符数组的内容,然后复制临时对象的内存地址。