Next: Using gcc for Syntax Checking, Previous: Style Checking, Up: Switches for gcc
By default, the following checks are suppressed: integer overflow checks, stack overflow checks, and checks for access before elaboration on subprogram calls. All other checks, including range checks and array bounds checks, are turned on by default. The following gcc switches refine this default behavior.
pragma Suppress (All_checks)
had been present in the source. Validity checks are also eliminated (in
other words -gnatp also implies -gnatVn.
Use this switch to improve the performance
of the code at the expense of safety in the presence of invalid data or
program bugs.
Note that when checks are suppressed, the compiler is allowed, but not required, to omit the checking code. If the run-time cost of the checking code is zero or near-zero, the compiler will generate it even if checks are suppressed. In particular, if the compiler can prove that a certain check will necessarily fail, it will generate code to do an unconditional “raise”, even if checks are suppressed. The compiler warns in this case. Another case in which checks may not be eliminated is when they are embedded in certain run time routines such as math library routines.
Of course, run-time checks are omitted whenever the compiler can prove that they will not fail, whether or not checks are suppressed.
Note that if you suppress a check that would have failed, program
execution is erroneous, which means the behavior is totally
unpredictable. The program might crash, or print wrong answers, or
do anything else. It might even do exactly what you wanted it to do
(and then it might start failing mysteriously next week or next
year). The compiler will generate code based on the assumption that
the condition being checked is true, which can result in disaster if
that assumption is wrong.
Constraint_Error
as required by standard Ada
semantics). These overflow checks correspond to situations in which
the true value of the result of an operation may be outside the base
range of the result type. The following example shows the distinction:
X1 : Integer := "Integer'Last"; X2 : Integer range 1 .. 5 := "5"; X3 : Integer := "Integer'Last"; X4 : Integer range 1 .. 5 := "5"; F : Float := "2.0E+20"; ... X1 := X1 + 1; X2 := X2 + 1; X3 := Integer (F); X4 := Integer (F);
Note that if explicit values are assigned at compile time, the compiler may be able to detect overflow at compile time, in which case no actual run-time checking code is required, and Constraint_Error will be raised unconditionally, with or without -gnato. That's why the assigned values in the above fragment are in quotes, the meaning is "assign a value not known to the compiler that happens to be equal to ...". The remaining discussion assumes that the compiler cannot detect the values at compile time.
Here the first addition results in a value that is outside the base range
of Integer, and hence requires an overflow check for detection of the
constraint error. Thus the first assignment to X1
raises a
Constraint_Error
exception only if -gnato is set.
The second increment operation results in a violation of the explicit range constraint; such range checks are performed by default, and are unaffected by -gnato.
The two conversions of F
both result in values that are outside
the base range of type Integer
and thus will raise
Constraint_Error
exceptions only if -gnato is used.
The fact that the result of the second conversion is assigned to
variable X4
with a restricted range is irrelevant, since the problem
is in the conversion, not the assignment.
Basically the rule is that in the default mode (-gnato not used), the generated code assures that all integer variables stay within their declared ranges, or within the base range if there is no declared range. This prevents any serious problems like indexes out of range for array operations.
What is not checked in default mode is an overflow that results in
an in-range, but incorrect value. In the above example, the assignments
to X1
, X2
, X3
all give results that are within the
range of the target variable, but the result is wrong in the sense that
it is too large to be represented correctly. Typically the assignment
to X1
will result in wrap around to the largest negative number.
The conversions of F
will result in some Integer
value
and if that integer value is out of the X4
range then the
subsequent assignment would generate an exception.
Note that the -gnato switch does not affect the code generated
for any floating-point operations; it applies only to integer
semantics).
For floating-point, GNAT has the Machine_Overflows
attribute set to False
and the normal mode of operation is to
generate IEEE NaN and infinite values on overflow or invalid operations
(such as dividing 0.0 by 0.0).
The reason that we distinguish overflow checking from other kinds of range constraint checking is that a failure of an overflow check, unlike for example the failure of a range check, can result in an incorrect value, but cannot cause random memory destruction (like an out of range subscript), or a wild jump (from an out of range case value). Overflow checking is also quite expensive in time and space, since in general it requires the use of double length arithmetic.
Note again that -gnato is off by default, so overflow checking is
not performed in default mode. This means that out of the box, with the
default settings, GNAT does not do all the checks expected from the
language description in the Ada Reference Manual. If you want all constraint
checks to be performed, as described in this Manual, then you must
explicitly use the -gnato switch either on the gnatmake or
gcc command.
The setting of these switches only controls the default setting of the
checks. You may modify them using either Suppress
(to remove
checks) or Unsuppress
(to add back suppressed checks) pragmas in
the program source.