c-非连续对象的数组
作者:互联网
#include <iostream>
#include <cstring>
// This struct is not guaranteed to occupy contiguous storage
// in the sense of the C++ Object model (§1.8.5):
struct separated {
int i;
separated(int a, int b){i=a; i2=b;}
~separated(){i=i2=-1;} // nontrivial destructor --> not trivially copyable
private: int i2; // different access control --> not standard layout
};
int main() {
static_assert(not std::is_standard_layout<separated>::value,"sl");
static_assert(not std::is_trivial<separated>::value,"tr");
separated a[2]={{1,2},{3,4}};
std::memset(&a[0],0,sizeof(a[0]));
std::cout<<a[1].i;
// No guarantee that the previous line outputs 3.
}
// compiled with Debian clang version 3.5.0-10, C++14-standard
// (outputs 3)
>削弱标准担保以至该程序可能表现出不确定的行为,其背后的原理是什么?
>标准说:
“数组类型的对象包含一个连续分配的N类型的N个子对象的非空集.” [dcl.array]§8.3.4.
如果类型T的对象不占用连续存储空间,那么此类对象的数组怎么办?
编辑:删除可能分散注意力的解释性文字
解决方法:
1.
这是巨龙采用的Occam剃刀实例,实际上它是编写编译器的:不要提供超过解决问题所需的保证,因为否则您的工作量将增加一倍,而无需补偿.问题的一部分是适合于高级硬件或历史性硬件的复杂类. (由BaummitAugen和M.M提示)
2.
(连续=共享公共边界,相邻或依次共享)
首先,不是T类型的对象总是或从不占用连续存储.单个二进制文件中的同一类型可能存在不同的内存布局.
[class.derived] §10 (8): A base class subobject might have a layout different from …
这足以使您退缩并满意我们的计算机上发生的事情与标准不矛盾.但是,让我们修改问题.更好的问题是:
Does the standard permit arrays of objects that do not occupy contiguous storage individually, while at the same time every two successive subobjects share a common border?
如果是这样,这将严重影响char *算术与T *算术的关系.
根据您是否理解OP标准报价(意味着只有子对象共享一个公共边界,或者在每个子对象内,这些字节也共享一个公共边界),您可能得出不同的结论.
假设第一个,您发现
“连续分配”或“连续存储”可能仅表示& a [n] ==& a [0] n(第23.3.2.1节),这是有关子对象地址的声明,并不表示该数组位于其中.连续字节的单个序列.
如果您使用更强的版本,则可能会得出T* versus char* pointer arithmetic年提出的’element offset == sizeof(T)’结论.
这也暗示着通过声明它们T t [1]可以迫使原本可能不连续的对象进入一个连续的布局.代替T t;
现在如何解决这个混乱局面?标准中对sizeof()运算符有一个根本上模棱两可的定义,这似乎是至少在每个体系结构上键入大致相等的布局的时间的遗留物,而不再是这种情况. (How does placement new know which layout to create?)
When applied to a class, the result [of sizeof()] is the number of bytes in an object of that class including any padding required for placing objects of that type in an array. [expr.sizeof] §5.3.3 (2)
但是,等等,所需的填充量取决于布局,并且单个类型可能具有多个布局.因此,我们势必会加一粒盐,并在所有可能的布局中采用最小的盐,或者做同样任意的事情.
最后,如果这是预期的含义,则数组定义将从char *算术方面的歧义中受益.否则,对问题1的答案将适用.
与现在删除的答案和评论有关的一些评论:
如Can technically objects occupy non-contiguous bytes of storage?中所述,实际上不存在连续的对象.此外,天真地设置子对象可能会使包含对象的不相关子对象无效,即使对于完全连续且可复制的对象也是如此:
#include <iostream>
#include <cstring>
struct A {
private: int a;
public: short i;
};
struct B : A {
short i;
};
int main()
{
static_assert(std::is_trivial<A>::value , "A not trivial.");
static_assert(not std::is_standard_layout<A>::value , "sl.");
static_assert(std::is_trivial<B>::value , "B not trivial.");
B object;
object.i=1;
std::cout<< object.B::i;
std::memset((void*)&(A&)object ,0,sizeof(A));
std::cout<<object.B::i;
}
// outputs 10 with g++/clang++, c++11, Debian 8, amd64
因此,可以想象问题栏中的内存集可能将a [1] .i设为零,这样程序将输出0而不是3.
在极少数情况下,根本不会在C对象中使用类似memset的函数. (通常,如果这样做,子对象的析构函数会公然失败.)但是有时人们希望在其析构函数中清除“几乎POD”类的内容,这可能是例外.
标签:memory-layout,c,arrays,language-lawyer,c14 来源: https://codeday.me/bug/20191013/1907830.html