为啥 <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&amp; 而不是 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 &amp; 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 的地址。请注意,只要myCharsconstmyCharsCopy 就必须是const。另请注意,您不能分配给数组,并且几乎不可能复制数组,除非您将其放在另一个数据结构中。您通常可以做的最好的事情是将数组中的内容复制到另一个数组(memcpystrcpy,具体取决于目标以及该数组是否为空字符数组)。

请注意,在许多用途中,例如函数参数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 循环对列表中的项目的副本进行操作。在正文中

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&amp;“修复”了这个问题,因为没有复制。每次迭代都对不同位置的不同对象进行操作,而不是对同一位置的不同对象进行操作。

第 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[] 行为如此不同?由于以下原因,我认为它们大致相同......

这是如上所述的数组衰减的结果。

旁白:

vectors 在被允许直接包含(和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&amp;,使用对原始内容的引用。因此,它们唯一且真实的内存地址被复制到列表中。

问题 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?

(const_cast<char*> 和没有之间有啥区别?

为啥“char*”可以指向“const char*”?

boost::variant - 为啥“const char*”转换为“bool”?

为啥 const char* 不能与 boost 的 stringstream 和 read_json 一起使用?