Skip to main content
GitHub

DCL02-F. Explicitly declare the 'save' attribute or split the variable initialization to prevent unintended behavior

In Fortran, when a variable is initialized at its declaration, it implicitly acquires the save attribute. This behavior obscures the program logic and is often unintended by the programmer, potentially breaking the program logic.

If the save behavior is intentional, explicitly add the attribute in the variable declaration to clarify the intent:

integer, save :: count = 0

Otherwise, split the initialization of the variable from its declaration to remove the implicit save behavior and prevent unintended effects.

A variable with the save attribute retains its value across multiple invocations of the procedure in which it is defined. The implicit save behavior can cause debugging difficulties, even when intended, and lead to hard-to-diagnose errors in program logic, particularly when working in complex codebases where functions are called multiple times during execution.

This behavior differs from languages like C, C++, or Java, where local variables initialized in a declaration are re-initialized on every scope entry.

Noncompliant Code Example

Consider the code below, which computes the sum of the elements of various arrays using the sum_array() function. Note how the variable result is set to 0 at its declaration, implicitly acquiring the save behavior:

Non-compliant code
! example.f90
program test_implicit_save
  implicit none
  integer, dimension(3) :: A = [1, 1, 1], B = [2, 2, 2]
  integer :: result

  result = sum_array(A)
  print *, "Sum of A:", result ! Expected: 3

  result = sum_array(B)
  print *, "Sum of B:", result ! Expected: 6

contains

  integer function sum_array(array)
    implicit none
    integer, intent(in) :: array(:)
    integer :: result = 0
    integer :: i

    do i = 1, size(array)
      result = result + array(i)
    end do

    sum_array = result
  end function sum_array

end program test_implicit_save

Implementation Details (Unix)

Each time sum_array() is called, one might expect result to be set to 0 and then add the elements of the target array. However, result retains its value between calls, breaking the intended logic for the program:

$ gfortran --version
GNU Fortran (Debian 12.2.0-14) 12.2.0
$ gfortran example.f90
$ ./a.out
 Sum of A:           3
 Sum of B:           9

Compliant Solution

While resolving the issue is as simple as splitting the initialization of result to a separate line, this type of bug can be particularly challenging to diagnose in complex codebases:

Compliant code
! solution.f90
program test_implicit_save
  implicit none
  integer, dimension(3) :: A = [1, 1, 1], B = [2, 2, 2]
  integer :: result

  result = sum_array(A)
  print *, "Sum of A:", result ! Expected: 3

  result = sum_array(B)
  print *, "Sum of B:", result ! Expected: 6

contains

  pure integer function sum_array(array)
    implicit none
    integer, intent(in) :: array(:)
    integer :: result
    integer :: i

    result = 0

    do i = 1, size(array)
      result = result + array(i)
    end do

    sum_array = result
  end function sum_array

end program test_implicit_save

Noncompliant Code Example

In this noncompliant example, the programmer intends for counter to be a local temporary variable that starts at 0 every time the subroutine is called. However, because it is initialized in the declaration statement, it implicitly has the save attribute.

The variable counter retains its value between calls due to the implicit save . If the subroutine process_data() is called twice with the dummy argument val set to the contanst value 5 , then the printed outputs will be 5 and 10. Without the save attribute, counterwould be set to 0 in each subroutine call and the printed output would be 5 and 5.

Non-compliant code
subroutine process_data(val)
  implicit none
  integer, intent(in) :: val
  ! Noncompliant: The variable implicitly has the save attribute
  integer             :: counter = 0

  counter = counter + val
  print *, "Cumulative internal value:", counter
end subroutine process_data

Compliant Solution

In this compliant solution, the programmer intends for the variable to be local and re-initialized to 0 in each subroutine call. Thus the initialization is moved to the execution statement, away from the declaration statement.

Compliant code
subroutine process_data(val, result)
  implicit none
  integer, intent(in)  :: val
  integer, intent(out) :: result
  integer              :: temp_val

  ! Compliant: The variable is not initialized in the declaration statement
  temp_val = 0
  temp_val = temp_val + val
  result = temp_val
end subroutine process_data

Compliant Solution

In this compliant solution, the programmer intends for the variable to be persistent (a "static" counter). Thus, the save attribute is explicitly declared to make the program logic clear to the reader.

Compliant code
subroutine process_data(val)
  implicit none
  integer, intent(in) :: val
  ! Compliant: The variable has the save attribute explicitly
  integer, save       :: counter = 0

  counter = counter + val
  print *, "Cumulative internal value:", counter
end subroutine process_data

Risk Assessment

Undefined behavior can produce incorrect results, silent data corruption, crashes, or nondeterministic behavior that varies across compilers or platforms. Programmers should ensure that the code avoids undefined behavior in all cases, including parallel and multi-threaded environments.

RecommendationSeverityLikelihoodDetectableRepairablePriorityLevel
DCL02-FHighLikelyYesYesP27L1

Attachments: