我正在审查其他人为我们的项目C++代码,该项目使用 MPI 进行高性能计算(10^5 - 10^6 个内核)。该代码旨在允许不同体系结构上的(可能)不同计算机之间的通信。他写了一条评论,内容如下:
我们通常使用
new
和delete
,但在这里我使用的是malloc
和free
。这是必要的,因为某些编译器在使用new
时会以不同的方式填充数据,从而导致在不同平台之间传输数据时出错。这不会发生在malloc
.
这与我从标准new
与malloc
问题中知道的任何内容都不符合。
new/delete和malloc/free有什么区别?暗示编译器可以以不同的方式计算对象的大小(但是为什么这与使用sizeof
不同呢?
malloc & placement new vs. new 是一个相当流行的问题,但只谈论new
使用构造函数,而malloc
没有,这与这个无关。
Malloc 如何理解对齐?说内存保证与new
或malloc
正确对齐,这是我之前的想法。
我的猜测是,他在过去一段时间误诊了自己的错误,并推断出new
和malloc
给出了不同数量的填充,我认为这可能不是真的。但我无法在谷歌或任何以前的问题中找到答案。
帮帮我,StackOverflow,你是我唯一的希望!
IIRC有一个挑剔的地方。malloc
保证返回与任何标准类型对齐的地址。::operator new(n)
仅保证返回与任何标准类型对齐的地址不大于 n,如果T
不是字符类型,则new T[n]
只需要返回与T
对齐的地址。
但这仅在您玩特定于实现的技巧时才有意义,例如使用指针的底部几位来存储标志,或者依赖地址来获得比严格需要的更多的对齐方式。
它不会影响对象中的填充,无论您如何分配它占用的内存,该对象都必须具有完全相同的布局。因此,很难看出这种差异如何导致传输数据时出错。
该评论的作者对堆栈或全局变量中的对象有何看法,无论在他看来它们是"像malloc一样填充"还是"像新的一样填充"?这可能会为这个想法的来源提供线索。
也许他很困惑,但也许他所说的代码不仅仅是malloc(sizeof(Foo) * n)
与new Foo[n]
之间的直接区别。也许它更像是:
malloc((sizeof(int) + sizeof(char)) * n);
与。
struct Foo { int a; char b; }
new Foo[n];
也就是说,也许他说的是"我使用 malloc",但意思是"我手动将数据打包到未对齐的位置,而不是使用结构"。实际上,手动打包结构不需要malloc
,但是没有意识到这一点是一种较小程度的混乱。有必要定义通过网络发送的数据布局。使用结构时,不同的实现将以不同的方式填充数据。
你的同事可能已经想到了new[]/delete[]
的魔术cookie(这是实现在删除数组时使用的信息)。但是,如果使用从new[]
返回的地址开始的分配(而不是分配器的地址),这将不是问题。
包装似乎更有可能。ABI 的变化(例如)可能导致在结构末尾添加不同数量的尾随字节(这受对齐方式的影响,也考虑数组)。使用malloc,可以指定结构的位置,从而更容易移植到国外的ABI。这些变化通常可以通过指定传输结构的对齐和包装来防止。
对象的布局不能取决于它是使用malloc
还是new
分配的。它们都返回相同类型的指针,当您将此指针传递给其他函数时,它们将不知道对象是如何分配的。sizeof *ptr
只是取决于ptr
的声明,而不是它是如何分配的。
我认为你是对的。填充由编译器完成,而不是new
或malloc
。即使您声明的数组或结构根本没有使用new
或malloc
,填充注意事项也适用。无论如何,虽然我可以看到在平台之间移植代码时,new
和malloc
的不同实现如何导致问题,但我完全看不出它们如何导致在平台之间传输数据的问题。
当我想控制我的普通旧数据结构的布局时,使用 MS Visual 编译器,我使用#pragma pack(1)
.我想大多数编译器都支持这样的预编译器指令,例如 gcc。
其结果是将结构的所有字段一个接一个地对齐,没有空格。
如果另一端的平台也这样做(即用填充1编译其数据交换结构),那么在两端检索的数据都非常适合。 因此,我从来没有在C++玩过malloc
。在最坏的情况下,我会考虑重载新运算符,以便它执行一些棘手的事情,而不是直接在C++中使用 malloc。
这是我对这个东西来自哪里的疯狂猜测。正如您提到的,问题出在通过 MPI 的数据传输上。
就个人而言,对于我想通过 MPI 发送/接收的复杂数据结构,我总是实现序列化/反序列化方法,将整个内容打包/解压缩到/从字符数组中打包/解压缩。现在,由于填充,我们知道结构的大小可能大于其成员的大小,因此还需要计算数据结构的未填充大小,以便我们知道发送/接收了多少字节。
例如,如果您想使用上述技术通过 MPI 发送/接收std::vector<Foo> A
,则假设生成的字符数组的大小通常是A.size()*sizeof(Foo)
错误的。换句话说,实现序列化/反序列化方法的每个类也应该实现一个报告数组大小的方法(或者更好地将数组存储在容器中)。这可能成为错误背后的原因。但是,无论如何,这与new
与malloc
无关,正如此线程中指出的那样。
在 c++:new
关键字中用于相对于某些数据结构分配一些特定的内存字节。例如,您已经定义了某个类或结构,并且想要为其对象分配内存。
myclass *my = new myclass();
或
int *i = new int(2);
但是在所有情况下,您都需要定义的数据类型(类,结构,联合,整数,字符等...),并且只会分配其对象/变量所需的内存字节。(即该数据类型的倍数)。
但是在使用 malloc() 方法的情况下,您可以分配任何内存字节,并且不需要始终指定数据类型。在这里,您可以在 malloc() 的几种可能性中观察到它:
void *v = malloc(23);
或
void *x = malloc(sizeof(int) * 23);
或
char *c = (char*)malloc(sizeof(char)*35);
>malloc 是一种函数 new 是 C++ 中的一种数据类型 在C ++中,如果我们使用malloc,那么我们必须并且应该使用Typecast,否则编译器会给你错误 如果我们使用新的数据类型来分配内存,则无需类型转换