在 Ada95 中,我们有两个相同类型的数组。如果我们像这样将一个分配给另一个:
Array_A := Array_B;
这究竟会做什么?
它会遍历 Array_B 并将每个元素依次分配给 Array_A 吗?还是只是将 Array_A 的地址更改为 Array_B 的地址?
询问的原因是我们需要知道这个赋值是否是原子操作。
在 Ada95 中,我们有两个相同类型的数组。如果我们像这样将一个分配给另一个:
Array_A := Array_B;
这究竟会做什么?
它会遍历 Array_B 并将每个元素依次分配给 Array_A 吗?还是只是将 Array_A 的地址更改为 Array_B 的地址?
询问的原因是我们需要知道这个赋值是否是原子操作。
假设
type My_Array is array (1 .. 42) of Integer;
Array_A : My_Array;
Array_B : My_Array;
thenArray_A
和Array_B
是不同的内存区域,并且在分配之后的字节Array_A
包含 的字节的副本Array_B
。传输是通过迭代元素还是通过某些等效的memcpy(3)
来完成的,取决于编译器编写者(pragma Atomic_Components
影响这一点)。
另一方面,如果你有
type My_Array is array (1 .. 42) of Integer;
type My_Array_P is access My_Array;
Array_A : My_Array_P;
Array_B : My_Array_P;
然后赋值将复制指针,并且可能是原子的(您可以添加pragma Atomic (My_Array_P);
或pragma Atomic (Array_A);
告诉编译器使访问值成为原子的,或者如果不能编译代码则无法编译)。
Array_A 的内容最终成为 Array_B 元素的副本,即赋值遍历数组,为每个元素赋值。与 C 不同,Ada 具有真正的数组对象。
因此,除非您在受保护的对象之类的东西中保护操作,否则分配不是原子的。(在受保护对象中包装数组赋值不会使其成为原子,它只是从应用程序的其余部分的角度来看。)
赋值语句复制数据。Ada 区分了保存数据的对象和仅“引用”数据的对象;作为引用的对象具有作为类型的access
类型(例如My_Array_P
在 Simon 的示例中)。分配访问类型会导致引用指向同一个对象;但是分配一个不是访问类型的对象总是会复制数据。
说数组赋值“在 Array_B 上迭代 [s] 并依次将每个元素分配给 Array_A”并不完全准确。如果数组元素类型是受控类型或具有受控子组件,则Array_A := Array_B;
必须最终确定 的元素,然后Array_A
必须调整它们,但它的发生顺序如下:首先,每个元素都Array_A
被最终确定(以任意顺序)。然后将数据从复制Array_B
到复制Array_A
(这可以一次完成一个元素,但在许多情况下,编译器可以将其优化为块复制)。然后调整 的每个元素Array_A
(同样,以任意顺序)。因此, 的语义与Array_A := Array_B
循环不同(我假设数组的上限和下限相同):
for I in Array_A'range loop
Array_A (I) := Array_B (I);
end loop;
因为Finalize
andAdjust
调用将以不同的顺序完成。通常这不应该对程序产生任何影响,如果Finalize
和Adjust
编写正确(如果效果不同,则程序设计可能有些奇怪)。但是,在Finalize
释放内存并Adjust
分配新内存的情况下(例如在典型Unbounded_String
实现中),以不同的顺序进行Finalize
andAdjust
调用可能会对内存碎片产生影响。
您的问题的答案是,如果您希望编译器对它们进行原子分配,您应该将对象声明为“原子”。
如果您的目标 CPU 没有适合对象类型的原子指令,它会抱怨。在这种情况下,您(至少)有一种替代解决方案:
使用访问类型和复制引用当然是一种选择,但它实际上是一种完全不同的操作,因此您应该仔细考虑这是否真的是您想要的。
(如果有问题的数组是短的、打包的布尔数组,您的编译器可能能够使对象“原子”,否则我不会期望它。)