Strange behaviour when using type(*) and character(*) in GFortran

I have a routine which accepts two variables, one is type(*) the other character(*).
If I pass character variables to both arguments, the length of the variable passed to the type(*) arguments determines the length of the variable of the character(*) argument.

E. g. if I do call sub("a", "123") then sub only receives “1” at the second argument, even if I never use the first argument. If the first argument is very long, the second also gets filled with junk.

Example code:

implicit none

character(100) :: not_used
call sub(not_used, "123")

contains

    subroutine sub(useless_var, print_this)
        type(*), intent(in)      :: useless_var
        character(*), intent(in) :: print_this
        print *, "should be 123: '", print_this, "'"
    end subroutine sub

end

Does anyone encounter a similar problem?

gcc (GCC) 13.1.1 20230714

1 Like

Hi @Carltoffel ,

I think that for making that sub interafce as general as possible, you need to make two slight changes:

  1. use class(*) instead of type(*) (hoping that you don’t actually use that variable in the procedure, but then why to even pass it in the first place… so I fear you actually do use it).
  2. make the useless var assumed rank, in order to work with both scalar/array entries.

This worked with gfortran trunk, ifort/ifx latest.

program main
   implicit none
   character(len = *), parameter :: not_used              = 'A very long, long, long, long string'
   character(len = 1), parameter :: not_used_chArray(115) = 'A very long, long, long, long string, but as an array'
   call sub(not_used, "123")
   call sub(not_used_chArray, "123")
contains
   subroutine sub(useless_var, print_this)
      class(*), intent(in)     :: useless_var(..)
      character(*), intent(in) :: print_this
      print *, "should be 123:  '", print_this, "' "
   end subroutine sub
end

I tested using type(*), and ifort/ifx actually give the expected output. Don’t know at this point if it is a gfortran bug there. Might need to wait someone else’s answer for that.

Hope this helps.

1 Like

@Carltoffel ,

For whatever it’s worth, I think your code conforms and Intel Fortran works as expected with it. You may want to post a support request with GCC Bugzilla.

1 Like

Thanks, this is an easy hack which should do the trick.

It actually is in the original code.

I don’t use the value, but I need it to check if maybe is unallocated. See your comment here.

A more complete example what I’m trying to do would be this:

implicit none

character(:), allocatable :: not_used
call sub(not_used, "123")
call sub("1", "123")

contains

    subroutine sub(useless_var, print_this)
        class(*), intent(in)      :: useless_var(..)
        character(*), intent(in) :: print_this
        print *, "should be 123: '", print_this, "'"
        print *, "useless_var is unallocated", is_unallocated(useless_var)
    end subroutine sub

    logical function is_unallocated(var)
        class(*), intent(in), optional :: var(..)
        is_unallocated = .not. present(var)
    end function is_unallocated

end

Followup question: Should I use type(*) or class(*) for the is_unallocated function? What’t the difference beside bypassing a GFortran bug?

1 Like

Thank you very much, I didn’t know this flag. Looks very useful!
However, I don’t really understand its output. Could you give me a hint, please?
For reference, I put the output into this collapsible section:

dump-tree
__attribute__((fn spec (". r r ")))
void sub (struct array15_unknown & restrict useless_var, character(kind=1)[1:_print_this] & restrict print_this, integer(kind=8) _print_this)
{
  bitsizetype D.4290;
  sizetype D.4291;

  D.4290 = (bitsizetype) (sizetype) NON_LVALUE_EXPR <_print_this> * 8;
  D.4291 = (sizetype) NON_LVALUE_EXPR <_print_this>;
  {
    struct __st_parameter_dt dt_parm.0;

    dt_parm.0.common.filename = &"a.f90"[1]{lb: 1 sz: 1};
    dt_parm.0.common.line = 11;
    dt_parm.0.common.flags = 128;
    dt_parm.0.common.unit = 6;
    _gfortran_st_write (&dt_parm.0);
    _gfortran_transfer_character_write (&dt_parm.0, &"should be 123: \'"[1]{lb: 1 sz: 1}, 16);
    _gfortran_transfer_character_write (&dt_parm.0, print_this, _print_this);
    _gfortran_transfer_character_write (&dt_parm.0, &"\'"[1]{lb: 1 sz: 1}, 1);
    _gfortran_st_write_done (&dt_parm.0);
  }
}


__attribute__((fn spec (". ")))
void MAIN__ ()
{
  static void sub (struct array15_unknown & restrict, character(kind=1)[1:] & restrict, integer(kind=8));
  character(kind=1) not_used[1:100];

  {
    struct array00_character(kind=1) desc.1;

    desc.1.dtype = {.elem_len=100, .rank=0, .type=6};
    desc.1.data = (void * restrict) &not_used;
    desc.1.span = (integer(kind=8)) desc.1.dtype.elem_len;
    sub (&desc.1, &"123"[1]{lb: 1 sz: 1}, 100, 3);
  }
}


__attribute__((externally_visible))
integer(kind=4) main (integer(kind=4) argc, character(kind=1) * * argv)
{
  static integer(kind=4) options.2[7] = {2116, 4095, 0, 1, 1, 0, 31};

  _gfortran_set_args (argc, argv);
  _gfortran_set_options (7, &options.2[0]);
  MAIN__ ();
  return 0;
}

I can confirm that Intel compiles this correctly.
I wanted to open a bug, but the bug reporting website looks a bit scary to me, and it recommends using a secondary e-mail (because of spam etc.) which I currently don’t have. So I may get to it later.

Thanks for this very interesting introduction to the GFortran tree dump. It’s cool to see the hidden arguments. I have heard about them here and there, and now here they are!

1 Like

@Carltoffel ,

I think they are interchangeable in such context (as in others as well). So, you can choose whichever you like the most.

1 Like

Note that the argument to sub must be allocatable or optional then in your example. As written the call to is_unallocated should always return .true., as it is always present, but you may get a segfault if the actual argument to sub isn’t allocated. I.e. it should be

    subroutine sub(useless_var, print_this)
        type(*), intent(in), optional      :: useless_var(..)
        character(*), intent(in) :: print_this
        print *, "should be 123: '", print_this, "'"
        print *, "useless_var is unallocated", is_unallocated(useless_var)
    end subroutine sub

Note that type(*) is fine in this case if you don’t intend to use its value.

Do you mean that it is against the standard to pass an unallocated variable as an actual argument to a dummy argument that is neither allocatable nor optional? I think I understand what you mean, but it worked in GFortran. Maybe it’s undefined behaviour?

Since many people have asked why would you need such a function, here’s what I do:
I’ve written a program that stores all arguments of a subroutine in a netCDF file. Since some arguments are deeply nested derived types, I automated the process by parsing the source code and generating function calls to my netCDF library. For each Fortran data type within the derived types, I generate the necessary variable definition (including its dimensions) and storage of each variable. (In some cases, there are over 100 individual variables). Sometimes, a derived type is passed to a subroutine where some members are not allocated because they aren’t used in the subroutine. My plan is to skip all unallocated variables, because the parser that generates the function calls has no way of knowing whether an allocatable variable is allocated or not.

Correct. If it worked with gfortran I would say it was coincidental, and that turning on certain run-time checking may have flagged it. Sounds like an interesting use case though.

1 Like

Makes totally sense because in many situations the compiler cannot know whether it is allocated or not. :+1:

@Carltoffel ,

Sorry but still doesn’t make sense. What you show doesn’t appear any further functional than the ALLOCATED intrinsic.

Unless you have a function which accepts both allocatable and not allocatable variables. You cannot call allocated on a variable which isn’t allocatable.

‘array’ argument of ‘allocated’ intrinsic at (1) must be ALLOCATABLE

I’m referring to your explanation here and particularly with, “a derived type is passed to a subroutine where some members are not allocated because they aren’t used in the subroutine.” and with functions such as is_allocated in conjunction with procedures such as your sub above.

My point is you’re after a certain edifice with netCDF and your aspects such as is_allocated with a sub that takes unlimited polymorphic are unclear; is_allocated does not make sense. Perhaps you relook what you are after from this perspective, you may find a way to work with ALLOCATED intrinsic.

Perhaps I haven’t made it clear enough what the situation is.

I have a large Chemistry Transport Model (CTM) which consists of many subroutines. I want to work on a subroutine without having to run the whole model (which takes hours even on 500+ CPU cores).
For example, I have a target routine like this:

subroutine target(a, b, c)
integer :: a
real :: b
type(t1) :: c
end subroutine

I parse the relevant types of the code with a Python script, which generates another subroutine store_target_netcdf with exactly the same arguments. But within this subroutine, each argument is written to a netCDF file.

...
call define_var(a, "a")
call define_var(b, "b")
call define_var(c%sub1, "c/sub1")
call define_var(c%sub2%a "c/sub2/a")
...
call store_var(a, "a")
call store_var(b, "b")
...

So there is one interface that simply accepts variables of any type and rank, and under the hood distinguishes between type and rank to define the correct netCDF variable and call the correct netCDF library functions.

In the CTM, all I have to do is change the call from target to store_target_netcdf and the next model run will generate a loadable checkpoint anywhere I want.

But when there are variables which aren’t allocated, trying to store them results obviously in segfaults.

It would appear you are after a specific form of serialization around your need with Fortran subprograms.

You may want to consider some specific binary format or a literal string or some such that helps you with, “variables which aren’t allocated.”

That is, pick some idea(s) for your specific case from what others have done with object-oriented languages and serialization toward netCDF, etc. in order to handle objects that are not instantiated.