Skip to main content
GitHub

DCL04-F. Conditionally initialized variables can lead to undefined behavior

A variable is initialized only under certain conditions. If there are control flow paths that read the variable when uninitialized, this can lead to undefined behavior due to its indeterminate value.

To prevent bugs in the code, ensure the problematic variable is initialized in all possible code paths. It may help to add explicit else or default branches in control-flow blocks, or even set a default initial value immediately after declaring the variable.

Some programming languages automatically initialize variables to default values, but Fortran, C, and C++ often do not. Consequently, variables left uninitialized contain unpredictable data until they are explicitly set by the programmer. In Fortran, reading any variable that has not been explicitly initialized results in undefined behavior.

Since uninitialized variables may contain any arbitrary values, reading and using them can lead to undefined behavior, potentially causing incorrect results, crashes, or other unintended outcomes. Compilers are not required to warn about these issues and, even if they do, they typically still allow the code to compile and run.

Some compilers may appear to "help" by zero-initializing variables under certain conditions (e.g., specific compilation flags). While this can make technically incorrect code run as originally intended, relying on such incidental behavior creates a false sense of security and masks underlying logical errors. Ultimately, this can vanish under different compilation settings and can vary between compilers.

Lastly, while some compilers provide options to automatically initialize certain data types (e.g., gfortran's -finit-integer=<value> or gcc's -ftrivial-auto-var-init=<value>), these features reduce portability to other development environments and hide problems in the code rather than addressing them.

Noncompliant Code Example

Consider the following code, which sums the elements of an array after applying the specified transformation:

Non-compliant code
! example.f90

module options
  implicit none
  integer, parameter :: OPTION_HALF   = 1
  integer, parameter :: OPTION_DOUBLE = 2
  integer, parameter :: OPTION_UNKNOWN = 3
end module options

program main
  use iso_fortran_env, only: real32
  use options, only: OPTION_HALF, OPTION_DOUBLE, OPTION_UNKNOWN
  implicit none

  real(kind=real32) :: array(4)
  array = [0.25, 0.25, 0.25, 0.25]

  print *, "Sum is:", transform_and_sum(array, OPTION_UNKNOWN)

contains

  pure real(kind=real32) function transform_and_sum(array, option)
    implicit none

    real(kind=real32), intent(in) :: array(:)
    integer, intent(in) :: option

    real(kind=real32) :: sum
    real(kind=real32) :: factor
    integer :: i

    sum = 0.0

    if (option == OPTION_HALF) then
      factor = 0.5
    else if (option == OPTION_DOUBLE) then
      factor = 2.0
    end if

    do i = 1, size(array, 1)
      sum = sum + array(i) * factor
    end do

    transform_and_sum = sum
  end function transform_and_sum

end program main

Note how factor is only explicitly initialized when the received option is known. Since the Fortran standard does not guarantee any specific initial value, the state of factor is indeterminate in the previous scenario (using OPTION_UNKNOWN), leading to different outcomes depending on the compiler settings.

Implementation Details (Unix)

Consider the following compiler settings that produce different results. For instance, gfortran -O2 behaves as if the if branch was taken:

$ gfortran --version
GNU Fortran (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0
$ gfortran -O2 example.f90 -o example_gfortran
$ ./example_gfortran 
 Sum is:   0.500000

However, flang -O2 behaves as if the else if branch was taken:

$ flang-new --version
Ubuntu flang-new version 18.1.3 (1ubuntu1)
$ flang-new -O2 example.f90 -o example_flang
$ ./example_flang 
 Sum is: 2.

It is important to note that the compiler is not "randomly choosing" a branch to execute. Instead, an undefined behavior means the outcome of the code is unpredictable, and could even lead to completely incorrect results or crashes. In other words, code with undefined behavior should never be relied upon.

Compliant Solution

To prevent these problems, we must always initialize variables before using them. This principle applies to all variable types, including other elemental types like integer, derived types, and arrays.

There are multiple ways to solve the bug. For example, we can simply initialize factor with a default value after its declaration, ensuring it is always defined regardless of the received option:

Compliant code
! solution.f90
real(kind=real32) :: factor

! Identity transformation by default
factor = 1.0

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.

RecommendationSeverityLikelihoodDetectableRepairablePriorityLevel
DCL04-FHighLikelyYesYesP27L1

Attachments: