可以使用memcpy()来复制包含指针的结构吗

Is it alright to use memcpy() to copy a struct that contains a pointer?

本文关键字:指针 包含 结构 复制 memcpy 可以使      更新时间:2023-10-16

前几天我在想这个问题,我很好奇这是不是个坏主意。。。假设有一个结构包含一个指向字符串数组的指针。memcpy()会复制下面示例中的"name"数组指针吗?编辑:在本例中,std是不可访问的。

struct charMap
{ 
unsigned char * name;   
unsigned char id;       
};
typedef struct charMap CharMapT;
class ABC
{
public:
ABC(){}
void Function();
CharMapT* structList;
}
void ABC::Function ()
{
CharMapT list[] = 
{
{"NAME1", 1},
{"NAME2", 2}, 
{"NAME3", 3}
};
structList = new CharMapT[sizeof(list)];
memcpy(structList, &list, sizeof(list));
}

给出的代码中有几个错误,我将首先讨论这些错误,然后是我对指针与数组的总结。

struct charMap
{ 
unsigned int * name;   
unsigned int id;       
};
typedef struct charMap CharMapT;

这声明了一个结构类型,其中包括一个指向无符号int的指针作为第一个成员(name),以及一个指向第二个成员(id)的int。在具有默认字节封装的32位系统上,其宽度为8字节(32位指针=4字节,32位带符号int=4字节)。如果这是一台64位机器,指针将为8字节宽,int仍然可能为32位宽,使结构大小为12字节

可疑代码

void ABC::Function ()
{
CharMapT list[] = 
{
{"NAME1", 1},
{"NAME2", 2}, 
{"NAME3", 3}
};
structList = new CharMapT[sizeof(list)];
memcpy(structList, &list, sizeof(list));
}

这将分配CharMapT结构的动态数组。有多少比你想象的要多。sizeof(list)将返回list[]数组的字节计数。由于CharMapT结构是8个字节宽(见上文),这将是3*8,或24个CharMapT项(如果使用64位指针,则为36项)。

然后,我们从list(&list中的&是不必要的)到新分配的存储器的memcpy()24字节(或36字节)。这将复制3个以上的CharMapT结构,使我们分配的其他21个结构保持不变(超出了它们最初的默认构造)。

注意:您正在将const char *初始化为一个声明为unsigned int *的字段,因此,如果这是编译的,那么基本数据类型将有所不同。假设您修复了结构并将指针类型更改为const char *,则const数据段中某个位置的静态字符串常量的地址("NAME"常量的地址)将分别分配给structList[0].NAME、structList[2].NAME和structList[3].NAME中元素的指针变量。

这不会将指向的数据复制到。它将只复制指针。如果您想要数据的副本,那么您必须对其进行原始分配(malloc、new等等)。

更好的是,使用std::vector<CharMapT>,对CharMapT::name使用std::string,并使用std::copy()复制源(甚至直接分配)。

我希望这能解释你在寻找什么。


指针与阵列对角线

切勿将指针与数组混淆。指针是变量保存地址。就像int变量保存整数值,或者char变量保存字符类型一样,指针中保存的值是地址

数组不同。它也是一个变量(很明显),但它不可能是一个l值,而且几乎每个通常使用它的地方都会发生转换。从概念上讲,转换会产生一个临时指针,指向数组的数据类型,并保存第一个元素的地址。有时,这个概念不会发生(例如应用运算符的地址)。

void foo(const char * p)
{
}
char ar[] = "Hello, World!";
foo(ar); // passes 'ar', converted to `char*`, into foo. 
// the parameter p in foo will *hold* this address

或者这个:

char ar[] = "Goodbye, World!";
const char *p = ar;  // ok. p now holds the address of first element in ar
++p;                 // ok. address in `p` changed to address (ar+1)

但不是这个:

char ar[] = "Goodbye, World!";
++ar; //  error. nothing to increment.

它不会复制name指向的实际数据。它将复制指针,并且在2个对象中有2个指向同一位置的指针(对于2个数组中的每对对象)。

这里您真正需要知道的是,memcpy将为您提供原始文件的逐位副本。因此,您将拥有两个具有相同值(即地址)的指针,它们引用相同的数据。

附带说明一下,您已经将name声明为指向int的指针,这在这里当然是错误的。它应该是const char*。此外,由于这是C++而不是C,所以最好使用std::copy之类的东西,如果charMap有朝一日成为复杂类型,它不会巧妙地破坏代码。同样,在大多数情况下,更喜欢std::string而不是const char*

调用new时,您对sizeof()的使用是错误的。您正在分配一个CharMapT元素数组。您必须指定元素的数量,但您指定的是字节计数。所以你需要解决这个问题:

structList = new CharMapT[sizeof(list) / sizeof(CharMapT)];

固定后,memcpy()的结果将是structList将包含list[]所包含的原始数据的精确副本。这意味着structList[N].name指针将包含与list[N].name指针相同的值,因此它们都将指向字符串值的相同物理内存。

如果你想对字符串值进行深度复制,你必须分别分配它们,例如:

void ABC::Function ()
{
CharMapT list[] = 
{
{"NAME1", 1},
{"NAME2", 2}, 
{"NAME3", 3}
};
int num = sizeof(list) / sizeof(CharMapT);
structList = new CharMapT[num];
for (int i = 0; i < num; ++i)
{
int len = strlen(list[i].name);
structList[i].name = new char[len+1];
strcpy(structList[i].name, list[i].name);
structList[i].name[len] = 0;
structList[i].id = list[i].id;
}
...
for (int i = 0; i < num; ++i)
delete[] structList[i].name;
delete[] structList;
}

我想添加到@EdSs的答案:

如果你这样做的话,你的代码比c风格的c++代码要多得多:

#include<string>
#include<vector>
struct CharMap
{ 
CharMap(const std::string& name, unsigned char id); // only needed if you don't use -std=c++11
std::string name;   
unsigned char id;       
};
CharMap::CharMap(const std::string& name, unsigned char id):
name(name),
id(id)
{}
class ABC
{
public:
ABC(); // or ABC() = default; if you use -std=c++11
void Function();
private:
std::vector<CharMap> structList;
}
ABC::ABC(){} // not needed with -std=c++11
void ABC::Function ()
{
// This works with -std=c++11:
//structList = 
//{
//  {"NAME1", 1},
//  {"NAME2", 2}, 
//  {"NAME3", 3}
//}; 
// without c++11:
structList = std::vector<CharMap>(3);
structList[0] = CharMap("NAME1",1); // don't worry about copies, we have RVO (check wikipedia or SO)
structList[1] = CharMap("NAME2",2);
structList[2] = CharMap("NAME2",3);
}

为什么不使用std::vector制作阵列?你可以这样做:

#include<vector>
std::vector<CharMapT> structList(list.size()); 

更安全的是,避免使用指针可以减少由于错误使用sizeof运算符而导致内存泄漏或出现错误的几率。

我想你并不是真的想要structList,它的元素数量与列表的内存大小一样多。(如果列表是双倍的,这可能是列表中元素数量的数倍。)

此外,若列表也是一个向量(实际上是一个c函数),则memcpy实际上是不必要的。你只需要做一个简单的分配操作:

structList = list; // given that list is a vector.

这将复制memcpy等元素。