Intel® Fortran Compiler
Build applications that can scale for the future with optimized code designed for Intel® Xeon® and compatible processors.

Memory Address Unexpectedly Changes

WileyOne
Novice
1,619 Views

Compiler: ifx (IFX) 2025.0.4

OS: Rocky Linux 8.9 (Green Obsidian)

 

I’ve got another strange one. The code compiles but gives a bogus memory address when printed. In short, one of the pointers inexplicably reassigns to a different patch of memory.

 

The following is a much-reduced example from our team’s architecture; it is longer than I would like, but I could not find a more streamlined way to demonstrate the bug. The memory address printed at lines 132, 133, 141, and 142 should all be the same, but the last one is different. While the current example does not produce a runtime error, it did err at different stages as I slowly paired down the original case into this example.

module Actor_Header

    implicit none

    type :: Actor
        integer :: ii
    end type

    type ActorContainerElement
        class(Actor),                pointer :: object => null()
        type(ActorContainerElement), pointer :: ptr    => null() ! comment out this line and everything is fine
    end type

    type ActorContainer
        type(ActorContainerElement), pointer, public :: item => null()
        contains
        final              :: finalize
        generic,   public  :: assignment(=) => copyContainer
        procedure, public  :: add
        procedure, private :: copyContainer
    end type

    contains

    subroutine finalize(this)
        type(ActorContainer) :: this

        deallocate(this%item)
    end subroutine

    subroutine copyContainer(lhs,rhs)
        class(ActorContainer), intent(out) :: lhs
        class(ActorContainer), intent(in)  :: rhs

        call lhs%add(rhs%item%object)
    end subroutine

    subroutine add(this,something)
        class(ActorContainer), target, intent(inout) :: this
            class(Actor), target, intent(in) :: something

        allocate(this%item)
        this%item%object => something
    end subroutine

end module

module Class1_Header

    implicit none

    type :: Class1
        logical :: bool = .false.
    end type

    type Class1ContainerElement
        class(Class1),            allocatable :: object
        type(Class1ContainerElement), pointer :: ptr => null() ! comment out this line and everything is fine
    end type

    type Class1Container
        type(Class1ContainerElement), pointer :: item => null()
        contains
        procedure, public :: add
    end type

    contains

    subroutine add(this,something)
        class(Class1Container), intent(inout) :: this
        class(Class1),          intent(in)    :: something

        allocate(this%item)
        this%item%object =  something
    end subroutine

end module

module Class2_Header

    use Actor_Header

    implicit none

    type :: Filter1
        type(ActorContainer) :: actors
    end type

    type :: CompositeFilter
        type(Filter1) :: subFilter
    end type

    type :: Class2
        type(CompositeFilter), allocatable :: filter
    end type

    contains

    function NewClass2(anActor) result(this)
        class(Actor), intent(in) :: anActor
        type(Class2)             :: this

        allocate(this%filter)
        call this%filter%subFilter%actors%add(anActor)
    end function

end module

program main

    use Actor_Header
    use Class1_Header
    use Class2_Header

    implicit none

    type(Actor)  :: anActor
    type(Class2) :: myClass2
    class(Actor), pointer :: pActor => null()

    type(Class1)          :: myClass1
    type(class1Container) :: myClass1Container

    print *, 'the following 4 memory addresses should be the same'

    ! create nested class structure
    anActor%ii = 41
    myClass2   = NewClass2(anActor)

    ! compare address of original object to underlying pointer
    pActor => myClass2%filter%subFilter%actors%item%object
    print *, '  actor address', loc(anActor)
    print *, 'pointer address', loc(pActor)

    ! add a totally unrelated object to a totally unrelated container
    !   comment out this line and the memory address issue is fixed...
    call myClass1Container%add(myClass1)

    ! compare address of original object to underlying pointer
    pActor => myClass2%filter%subFilter%actors%item%object
    print *, '  actor address', loc(anActor)
    print *, 'pointer address', loc(pActor)

    ! check that original object still exists (it does)
    anActor%ii = anActor%ii + 1
    print *, 'original object', anActor%ii, ' (should be 42 if original object still exists)'

end program

This code is also attached in a file below. Any and all help is appreciated.

 

Additional Details

There are several similarities between this bug and another issue I submitted but also some key difference. The main contrast is that the previous bug dealt with the wrong procedure being called, while this one shows a pointer is being unintentionally altered.

 

Note some of the oddities I observed when pairing down our architecture to the given example:

  1. Commenting out line 137 causes the error to vanish, yet it has nothing to do with the rest of the example.
  2. Commenting out line 11 or 58 (or both) also fixes the problem.

 

These are not realistic workarounds for our situation; again, this is a significant simplification from our framework. Certain aspects seem silly separated from their context. I am simply explaining why certain features were included in the bugged example.

Labels (1)
0 Kudos
6 Replies
WileyOne
Novice
1,511 Views

Note that this is stilled bugged in 2025.1.0

0 Kudos
WileyOne
Novice
1,232 Views

I found the underlying issue here. In short, the defined assignment operator is not called within the nested class structure. This is why the memory address was sometimes wrong in the above code. Here is a simplified example that shows that the defined assignment is not called, which is a bug:

module Classes_Header

    implicit none

    type :: class1
        integer :: ii
        contains
        generic,   public  :: assignment(=) => copy
        procedure, private :: copy
    end type

    type :: class2
        type(class1) :: myClass1
    end type

    type :: class3
        type(class2) :: myClass2
    end type

    type :: class4
        type(class3), allocatable :: myClass3
    end type

    contains

    subroutine copy(lhs,rhs)
        class(class1), intent(out) :: lhs
        class(class1), intent(in)  :: rhs

        lhs%ii = 7
        print *, 'copy was called'
    end subroutine

    function constructor() result(this)
        type(class4) :: this

        allocate(this%myClass3)
        this%myClass3%myClass2%myClass1%ii = 42
    end function

end module

program main

    use Classes_Header

    implicit none
    type(class4) :: myClass4

    myClass4 = constructor()

end program

Note the similarities and differences between this and another issue I just posted. In both issues, the defined assignment is not called. Here, the nested class structure contains an `allocatable` class, while the other issue deals with a parameterized derived type.
 

This is still bugged in 2025.2.0.

0 Kudos
Fengrui
Moderator
873 Views

Hi,

In the new example code, there is no explicit assignment ("="), something like 

-----

type(class1) :: obj1, obj2

obj1 = obj2

-----

so 'copy was called' is not printed.

 

In the original code, the problem is caused by a combination of many factors. When you do "myClass2 = NewClass2(anActor)", the return type Class2 is not trivial as it is related to ActorContainer <- ActorContainerElement which contains a pointer ptr. Then compiler creates a copy of the object of Class2 in the buffer. When calling myClass1Container%add(myClass1), the buffer got reused unexpectedly, affecting the pointer ptr in ActorContainerElement. 

A workaround could be changing NewClass2 from a function to a subroutine to avoid creating the first buffer.

-----

subroutine NewClass2(this, anActor)
class(Actor), intent(in) :: anActor
type(Class2), intent(out) :: this

allocate(this%filter)
call this%filter%subFilter%actors%add(anActor)
end subroutine

-----

Please give it a try in the larger code.

0 Kudos
WileyOne
Novice
861 Views

Hi Fengrui. I appreciate the response, but that is not how the Fortran standard defines assignment. Please see sections 10.2.1.3 (intrinsic assignment) and 10.2.1.4 (defined assignment) in the 2018 standard (I do not have a copy of the 2023 but assume the content is the same, though the section numbering may be different). The most important paragraph is 10.2.1.3.13:

"An intrinsic assignment where the variable is of derived type is performed as if each component of the variable
were assigned from the corresponding component of expr using pointer assignment (10.2.2) for each pointer
component, defined assignment for each nonpointer nonallocatable component of a type that has a type-bound
defined assignment consistent with the component, intrinsic assignment for each other nonpointer nonallocatable
component, and intrinsic assignment for each allocated coarray component. For unallocated coarray components,
the corresponding component of the variable shall be unallocated."

On line 50 of my most recent example, an intrinsic assignment is invoked on derived type class4. Therefore all "nonpointer nonallocatable component[s] of [that] type" should be defined via "defined assignment" if one is specified. Since class1 is a nested component of the structure, its defined assignment should be called. I agree that the defined assignment on class1 is not explicitly called anywhere, but it should be implicitly called when class4's intrinsic assignment is used on line 50. This is the bug.

 

Someone will probably think that defined assignment should not be called because class3 is an allocatable component. I would agree... if the defined assignment were on class3 and not class1. Per 15.4.3.4.3 note 2, defined assignment is not evoked when the left-hand side is allocatable. Fair enough. But the component myClass1 in class2 is not allocatable, so this does not apply.

 

The current behavior of ifx is not only inconsistent with the standard, but it is internally inconsistent with other code it produces. Defined assignment is called if I remove one class from the structure yet leave one of them as an allocatable component. Based on my understanding of the standard, ifx correctly compiles the following code which DOES call defined assignment:

module Classes_Header

    implicit none

    type :: class1
        integer :: ii
        contains
        generic,   public  :: assignment(=) => copy
        procedure, private :: copy
    end type

    type :: class2
        type(class1) :: myClass1
    end type

    type :: class3
        type(class2), allocatable :: myClass2
    end type

    contains

    subroutine copy(lhs,rhs)
        class(class1), intent(out) :: lhs
        class(class1), intent(in)  :: rhs

        lhs%ii = 7
        print *, 'copy was called'
    end subroutine

    function constructor() result(this)
        type(class3) :: this

        allocate(this%myClass2)
        this%myClass2%myClass1%ii = 42
    end function

end module

program main

    use Classes_Header

    implicit none
    type(class3) :: myClass3

    myClass3 = constructor()

end program

 

As for the pointer in my original code, please see another post of mine where I explain this under "additional details". Once you understand how defined assignment is supposed to operate, you will see that this is invalid behavior. If I did not have a defined assignment in that example, I completely agree that there would be a "dangling pointer". That is the purpose of the defined assignment: to perform a "deep" copy.

0 Kudos
Fengrui
Moderator
822 Views

Good point! The developer guide and reference also states that each component of the expression should be assigned.

That's true when the derived variable has more nested layers, the behavior becomes unexpected. I will report it as a bug to the team. Thanks!

0 Kudos
WileyOne
Novice
696 Views

Thank you, @Fengrui! I'm almost certain that fixing my updated, simplified example will solve the original problem.

 

Also, rereading the second sentence of my last paragraph, it sounds a bit harsh. I'm sorry; that was not my intention! Thank you for reporting this bug and the assistance. Happy 4th!

0 Kudos
Reply