Ada 中的无约束数组可以安全使用吗?
Are unconstrained arrays in Ada safe to use?
我正在阅读 Ada 中的数组类型,发现有趣的是,与 C++ 不同,该语言允许它们的大小在编译时未知。我不确定它们是如何实现的,所以我写了一个小测试:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Command_Line; use Ada.Command_Line;
procedure Main is
type Data is array (1 .. 131_072) of Integer;
type Vector is array (Positive range <>) of Data;
Sequence : Vector (1 .. Argument_Count);
begin
for I in Sequence'Range loop
Sequence (I) := (others => I);
Put (Integer'Image (Sequence (I)(1)));
end loop;
end Main;
然后尝试使用可变长度数组在 C 中复制此代码:
#include <stdio.h>
struct data {
int x[131072];
};
int main(int argc, char** argv) {
(void)argv;
struct data sequence[argc - 1];
for (int i = 0; i < argc - 1; i++) {
for (int j = 0; j < 131072; j++)
sequence[i].x[j] = i;
printf("%i ", sequence[i].x[1]);
}
}
我用 gnatmake -gnato -fstack-check -gnat2012 -gnata -O3 main.adb -o main
和 gcc -O3 -Wall -Werror -Wextra -pedantic cmain.c -o cmain
编译。在 运行 程序之后,当给定 16 个或更多参数时,两者都失败了 - 区别在于 cmain
只是段错误,而 main
最终引发“STORAGE_ERROR:堆栈溢出或错误内存访问。
由于 VLA 和无约束阵列似乎(至少在表面上)以类似的方式实现,并且前者被广泛认为几乎在所有情况下都不能安全使用,那么使用后者安全吗?
无约束数组本质上并非不安全。请参阅下面实现数组元素并行添加的示例。
package Parallel_Addition is
type Data_Array is array(Integer range <>) of Integer;
type Data_Access is access all Data_Array;
function Sum(Item : in not null Data_Access) return Integer;
end Parallel_Addition;
请注意,上面的包规范声明了一个不受约束的数组类型。它还声明了对无约束数组类型的访问类型,以便可以动态分配非常大的数组,避免堆栈耗尽问题。
package body Parallel_Addition is
---------
-- Sum --
---------
function Sum (Item : in not null Data_Access) return Integer is
task type Adder is
entry Set (Min : Integer; Max : Integer);
entry Report (Value : out Integer);
end Adder;
task body Adder is
Total : Integer := 0;
First : Integer;
Last : Integer;
begin
accept Set (Min : Integer; Max : Integer) do
First := Min;
Last := Max;
end Set;
for I in First .. Last loop
Total := Total + Item (I);
end loop;
accept Report (Value : out Integer) do
Value := Total;
end Report;
end Adder;
A1 : Adder;
A2 : Adder;
R1 : Integer;
R2 : Integer;
Mid : constant Integer := (Item'Length / 2) + Item'First;
begin
A1.Set (Min => Item'First, Max => Mid);
A2.Set (Min => Mid + 1, Max => Item'Last);
A1.Report (R1);
A2.Report (R2);
return R1 + R2;
end Sum;
end Parallel_Addition;
在函数 Sum 中使用无约束数组实例没有问题。
让我们尝试使用一个非常大的动态分配数组来测试这个包。
with Parallel_Addition; use Parallel_Addition;
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Calendar; use Ada.Calendar;
procedure Parallel_Addition_Test is
The_Data : Data_Access := new Data_Array (1 .. Integer'Last);
Start : Time;
Stop : Time;
The_Sum : Integer;
begin
The_Data.all := (others => 1);
Start := Clock;
The_Sum := Sum (The_Data);
Stop := Clock;
Put_Line ("The sum is: " & Integer'Image (The_Sum));
Put_Line
("Addition elapsed time is " &
Duration'Image (Stop - Start) &
" seconds.");
Put_Line
("Time per addition operation is " &
Float'Image(Float(Stop - Start) / Float(The_Data'Length)) &
" seconds.");
end Parallel_Addition_Test;
在我的 Windows 10 PC 上执行时,我得到以下输出:
The sum is: 2147483647
Addition elapsed time is 5.141288000 seconds.
Time per addition operation is 2.39410E-09 seconds.
Ada 中不受约束的数组类型本身并不安全;使用未经检查的输入值(例如 Argument_Count)来创建该大小的数组对象是不安全的,或者至少可能导致异常。如果你这样做,并且没有异常处理程序,攻击者可以使你的程序中止并出现未处理的异常,如你的示例所示。
请注意,在 Ada 中以两种方式使用不受约束的数组类型:
- 创建动态确定大小的数组对象,如您的示例所示。
- 将各种大小的数组作为参数传递给子程序,即使实际的数组对象具有静态定义的大小。
第二次使用(参数)当然是完全安全的,参数的实际边界可以像往常一样通过A'First、A'Last、A'Range、A'Length访问。
我正在阅读 Ada 中的数组类型,发现有趣的是,与 C++ 不同,该语言允许它们的大小在编译时未知。我不确定它们是如何实现的,所以我写了一个小测试:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Command_Line; use Ada.Command_Line;
procedure Main is
type Data is array (1 .. 131_072) of Integer;
type Vector is array (Positive range <>) of Data;
Sequence : Vector (1 .. Argument_Count);
begin
for I in Sequence'Range loop
Sequence (I) := (others => I);
Put (Integer'Image (Sequence (I)(1)));
end loop;
end Main;
然后尝试使用可变长度数组在 C 中复制此代码:
#include <stdio.h>
struct data {
int x[131072];
};
int main(int argc, char** argv) {
(void)argv;
struct data sequence[argc - 1];
for (int i = 0; i < argc - 1; i++) {
for (int j = 0; j < 131072; j++)
sequence[i].x[j] = i;
printf("%i ", sequence[i].x[1]);
}
}
我用 gnatmake -gnato -fstack-check -gnat2012 -gnata -O3 main.adb -o main
和 gcc -O3 -Wall -Werror -Wextra -pedantic cmain.c -o cmain
编译。在 运行 程序之后,当给定 16 个或更多参数时,两者都失败了 - 区别在于 cmain
只是段错误,而 main
最终引发“STORAGE_ERROR:堆栈溢出或错误内存访问。
由于 VLA 和无约束阵列似乎(至少在表面上)以类似的方式实现,并且前者被广泛认为几乎在所有情况下都不能安全使用,那么使用后者安全吗?
无约束数组本质上并非不安全。请参阅下面实现数组元素并行添加的示例。
package Parallel_Addition is
type Data_Array is array(Integer range <>) of Integer;
type Data_Access is access all Data_Array;
function Sum(Item : in not null Data_Access) return Integer;
end Parallel_Addition;
请注意,上面的包规范声明了一个不受约束的数组类型。它还声明了对无约束数组类型的访问类型,以便可以动态分配非常大的数组,避免堆栈耗尽问题。
package body Parallel_Addition is
---------
-- Sum --
---------
function Sum (Item : in not null Data_Access) return Integer is
task type Adder is
entry Set (Min : Integer; Max : Integer);
entry Report (Value : out Integer);
end Adder;
task body Adder is
Total : Integer := 0;
First : Integer;
Last : Integer;
begin
accept Set (Min : Integer; Max : Integer) do
First := Min;
Last := Max;
end Set;
for I in First .. Last loop
Total := Total + Item (I);
end loop;
accept Report (Value : out Integer) do
Value := Total;
end Report;
end Adder;
A1 : Adder;
A2 : Adder;
R1 : Integer;
R2 : Integer;
Mid : constant Integer := (Item'Length / 2) + Item'First;
begin
A1.Set (Min => Item'First, Max => Mid);
A2.Set (Min => Mid + 1, Max => Item'Last);
A1.Report (R1);
A2.Report (R2);
return R1 + R2;
end Sum;
end Parallel_Addition;
在函数 Sum 中使用无约束数组实例没有问题。 让我们尝试使用一个非常大的动态分配数组来测试这个包。
with Parallel_Addition; use Parallel_Addition;
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Calendar; use Ada.Calendar;
procedure Parallel_Addition_Test is
The_Data : Data_Access := new Data_Array (1 .. Integer'Last);
Start : Time;
Stop : Time;
The_Sum : Integer;
begin
The_Data.all := (others => 1);
Start := Clock;
The_Sum := Sum (The_Data);
Stop := Clock;
Put_Line ("The sum is: " & Integer'Image (The_Sum));
Put_Line
("Addition elapsed time is " &
Duration'Image (Stop - Start) &
" seconds.");
Put_Line
("Time per addition operation is " &
Float'Image(Float(Stop - Start) / Float(The_Data'Length)) &
" seconds.");
end Parallel_Addition_Test;
在我的 Windows 10 PC 上执行时,我得到以下输出:
The sum is: 2147483647
Addition elapsed time is 5.141288000 seconds.
Time per addition operation is 2.39410E-09 seconds.
Ada 中不受约束的数组类型本身并不安全;使用未经检查的输入值(例如 Argument_Count)来创建该大小的数组对象是不安全的,或者至少可能导致异常。如果你这样做,并且没有异常处理程序,攻击者可以使你的程序中止并出现未处理的异常,如你的示例所示。
请注意,在 Ada 中以两种方式使用不受约束的数组类型:
- 创建动态确定大小的数组对象,如您的示例所示。
- 将各种大小的数组作为参数传递给子程序,即使实际的数组对象具有静态定义的大小。
第二次使用(参数)当然是完全安全的,参数的实际边界可以像往常一样通过A'First、A'Last、A'Range、A'Length访问。