在C语言中,可以使用malloc(sizeof(T)* N)分配动态数组,然后使用指针算法在该动态数组的i偏移处获取元素。
在C ++中,可以使用运算符new()与malloc()相同的方式进行类似的操作,然后放置new(例如,可以在《 Exceptional C ++:47个工程难题,编程问题》一书中看到第13项的解决方案。和解决方案”。如果您没有,则此问题的解决方案摘要为:
T* storage = operator new(sizeof(T)*size);
// insert element
T* p = storage + i;
new (p) T(element);
// get element
T* element = storage[i];
对我来说,这看起来很合法,因为我要的是一个内存块,要有足够的内存来容纳N个对齐的size = sizeof(T)元素。由于sizeof(T)应该返回对齐的元素的大小,并且将它们一个接一个地放置在一块内存中,因此可以使用指针算法。
然后我却指向诸如http://eel.is/c++draft/expr.add#4或http://eel.is/c++draft/intro.object#def:object之类的链接,并声称在C ++运算符中new()不会返回数组对象,因此对返回的对象和使用它进行指针算术运算与ANSI C相反,数组是未定义的行为。
我在这样低级的课程上并不擅长,我确实想通过阅读以下内容来理解:https://www.ibm.com/developerworks/library/pa-dalign/或以下内容:http://jrruethe.github.io/blog/2015/08/23/placement-new/,但我仍然无法理解Sutter是否很简单错误吗?
我确实知道alignas在诸如以下的结构中是有意义的:
alignas(double) char array[sizeof(double)];
(c)http://georgeflanagin.com/alignas.php
如果数组似乎不在double的边界内(也许结构中的char跟随2字节读取处理器运行)。
但这是不同的-我从堆/空闲存储中请求了内存,特别是要求new的运算符返回内存,该内存将保存与sizeof(T)对齐的元素。
总结一下,如果这是TL; DR:
抱歉,这很愚蠢。
答案 0 :(得分:7)
分配的内存上的指针算术问题,如您的示例:
T* storage = operator new(sizeof(T)*size);
// ...
T* p = storage + i; // precondition: 0 <= i < size
new (p) T(element);
在技术上不确定的行为已经很久了。这意味着std::vector
不能仅以库的形式定义良好的行为来实现,而是需要实现中提供的标准之外的其他保证。
标准委员会绝对无意使std::vector
无法实施。萨特当然是正确的,这样的代码应该是打算。该标准的措辞需要反映这一点。
P0593是一项建议,如果被标准接受,则可以解决此问题。同时,可以像上面那样继续编写代码;没有主要的编译器会将其视为UB。
编辑:如评论中所指出,我应该说,当我说storage + i
在P0593下会得到很好的定义时,我假设元素{{1} },storage[0]
,...,storage[1]
已被构造。尽管我不确定我对P0593的理解是否足够,可以得出结论,它也不会涵盖那些尚未尚未构建的元素的情况。
答案 1 :(得分:2)
C ++标准包含一个开放的issue,对象的基础表示形式不是unsigned char
对象的“数组”而是“序列”。尽管如此,每个人都将其视为一个数组(这是预期的),因此可以安全地编写如下代码:
char* storage = static_cast<char*>(operator new(sizeof(T)*size));
// ...
char* p = storage + sizeof(T)*i; // precondition: 0 <= i < size
new (p) T(element);
,只要void* operator new(size_t)
返回正确对齐的值。使用sizeof
乘以偏移量来保持对齐方式是safe。
在C ++ 17中,有一个宏STDCPP_DEFAULT_NEW_ALIGNMENT,用于指定“正常” void* operator new(size_t)
的最大安全对齐方式;如果需要更大的对齐方式,则应使用void* operator new(std::size_t size, std::align_val_t alignment)
在早期版本的C ++中,没有这种区别,这意味着void* operator new(size_t)
的实现方式必须与任何对象的对齐方式兼容。
关于能够直接在T*
上进行指针算术,我不确定该标准是否需要 。但是,很难以无法使用的方式实现C ++内存模型。
答案 2 :(得分:0)
您可以使用“老式” malloc
来完成此操作,这将为您提供一块内存,可以满足各个平台上最严格的对齐方式(例如long long double
的对齐方式)。因此,您将能够将任何对象放入这样的缓冲区中,而不会违反任何对齐要求。
鉴于此,您可以基于这样的内存块对类型的数组使用new放置:
struct MyType {
MyType() {
cout << "in constructor of MyType" << endl;
}
~MyType() {
cout << "in destructor of MyType" << endl;
}
int x;
int y;
};
int main() {
char* buffer = (char*)malloc(sizeof(MyType)*3);
MyType *mt = new (buffer)MyType[3];
for (int i=0; i<3; i++) {
mt[i].~MyType();
}
free(mt);
}
请注意,与放置new一样,您必须小心地显式调用析构函数,并在不同的步骤中释放内存;您不能使用delete
或delete[]
函数,它们将这两个步骤结合在一起,从而释放了它们不拥有的内存。
答案 3 :(得分:0)
以下所有适用于最近广泛使用的posix兼容系统,即Windows,Linux(和Android ofc。)和MacOSX
是否可以在C ++中将malloc()用于动态数组?
是的。最佳做法是使用reinterpret_cast
将结果void*
转换为所需的指针类型,它会产生如下所示的动态分配数组:type *array = reinterpret_cast<type*>(malloc(sizeof(type)*array_size);
请注意,在这种情况下,不要在数组元素上调用构造函数,因此无论type
是什么,它都仍是未初始化的存储。当free
用于释放时,也不会调用析构函数。
在没有alignas关键字的旧C ++中,是否可以对动态数组使用运算符new()和new放置?
是的,但是如果您向其放置自定义位置(即,并非来自malloc / new的位置),则在放置新位置时需要注意对齐。普通运算符new和malloc将提供本机字对齐的内存区域(至少在分配大小> = wordize时)。这个事实以及确定结构布局和大小以便正确考虑对齐方式的事实,如果使用malloc或new,则无需担心dyn数组的对齐方式。
可能会注意到,字长有时会比最大的内置数据类型(通常为long double
)小很多,但由于对齐方式与数据大小无关,因此必须以相同的方式对齐,但是内存总线上不同访问大小的地址的位宽。
在运算符new()返回的内存上使用指针算术是未定义的行为吗?
不,只要您尊重进程的内存边界,从这个角度来看,new
的工作方式基本上与malloc
相同,而且,new
实际上在绝大多数实现都是为了获得所需的区域。
实际上,指针算法永远不会无效。但是,算术表达式的计算结果可能会指向一个指针,该结果可能指向允许区域之外的位置,但这不是指针算术的错误,而是错误表达式的错误。
萨特建议代码在某些古董机器上可能会中断吗?
我不这么认为,只要使用正确的编译器。 (请勿将AVR指令或128位宽的内存mov编译为旨在在80386上运行的二进制文件) 当然,在具有不同内存大小和布局的不同机器上,相同的文字地址可能会访问目的/状态/存在性不同的区域,但是为什么要使用文字地址,除非您将驱动程序代码写入特定的硬件?...:)< / p>