Why can't FORTRAN determine the size of a given C array?

I’m newish to fortran and trying to get a C program to call some f90 code that wasn’t written to be interfaced with C (as in, I don’t see bind(c) or c_double or any other telltales that I’ve seen in other code that presumably is meant to interop with C).

Right now I have a couple problems but I whittled down one of them to a simple reproducible program:

Here’s the fortran code:

subroutine mysub(a)
  integer :: a(:)
  print *, a(1)
  print *, size(a)
end subroutine mysub

It gets compiled with gfortran -c test.f90

Here’s the C code:

#include <stdlib.h>

extern void mysub_(int ** a);

int main() {
  int * a = malloc(4);
  a[0] = 42;
  mysub_(&a);
}

It gets compiled with gcc -lgfortran main.c test.o

This is the output:

          42
   257967009

Obviously the size is trash.

Where is fortran reading the size of the array from? Is there some standard memory location just before the array where I can sort of manually insert the size?

And if there are better ways to interop fortran and C I’d be happy to hear about it, although I’m not sure how much leeway I have to change the fortran code I’m using so any suggestions may have to be limited to the C side.

Cheers.

One reason for Fortran to be unable to know the size is that on the C side you cannot query it either. Yes, you allocated 4 bytes (!), nominally getting an array of 1 integer element, but if you were to pass the variable “a” to a C function, that function would not know the size either.
The clasical way to do this isto pass the size (that is, the number of elements, not the number of bytes) via a separate argument:

n = 1; /* See comment above */
mysub_( &a, n );

There is a second possibility, and you use that unknowingly by having an assumed-shape array in the Fortran routine in stead of an assumed-size array: construct a Fortran array descriptor on the C side and pass that. But either way, you have to prepare the information on the C side.

Another issue: you are explicitly using a common “decoration” on the C side. Many Fortran compilers add an underscore to their routines to avoid clashes with C libraries (runtime libraries of the OS included). You could use a wrapper around your Fortran routines to avoid this sort of things. Something along these lines:

subroutine mysub_wrapper( a, n ) bind(C, name = 'fmysub' )
     integer, value             :: n
     integer, dimension(n) :: a
     call mysub( a )

And on the C side:

n = 1
fmysub( &a, n );

(Note the attribute “value”)

There is more to say about this and the above is merely a sketch.

1 Like

I’m a bit confused here: a being a pointer, why should you pass its address &a rather than directly a?

Oh, that is ia stupid mistake. You are absolutely right. It should be:

fmysub( a, n );

(Hm, and what does the icon to my name signify? Looks like a piece of cake)

Actually passing &a is what the OP did, and it “worked” (well, partly: he got the right element value, not the size), that’s why I was confused. But they are using an assumed shape dummy on Fortran side, which is not interoperable without a descriptor on the C side, hence with unpredictable behavior. The UB may give the right value by chance.

Indeed, I did not even think of correcting that. My C is getting rusty or I need a holiday :slight_smile:

1 Like

Not sure if this is what the OP wants but F2018 provides a C_SIZEOF function that returns the same information that the C sizeof function does. For int C array of size 10 passed to Fortran as an interoperable type, C_SIZEOF would return a value of 40.

As far as I understand c_sizeof(), it can work only the dimension is passed in the first place.

That is correct but if you really need the size of the C array the only other way to do it is with the C descriptors from iso_fortran_binding.h. It boils down to what is the path of least resistance for the OP and what are his requirements. Personally I don’t find passing a single integer that defines the size to be a big deal.

Me neither. It’s just that I can’t see the added value brought by c_size in this context: if the size is explicitly passed then you don’t need c_size(), if the size is not passed then c_size() won’t work, and if the size is implicitly passed through a descriptor then the intrinsic Fortran size() will do.

The problem has nothing to do with Fortran, actually. It’s just the fact C doesn’t really have arrays, but rather “arrays”, essentially masqueraded pointers. Even if you defined a as int a[1], you shouldn’t expect this to behave as an actual array in every situation. I will try to explain below.

In your particular case, things are easier to explain: you don’t pass an array to the Fortran subroutine, you pass a C pointer. That pointer doesn’t keep any information about whether it just points to a single object or to the first object of an array. Therefore, Fortran cannot determine the size of the array, for the same reason even a C function woudn’t be able to do that. And even if you defined a as, say, int a[4], passing a to a C function actually passes a pointer, not an array. This is why determining its size in the receiving C function with something like

size_t size = sizeof(a)/sizeof(a[0])

is wrong, a common mistake that won’t do what you think at first glance.

Although I can think of some tricks that might be able to do what you want, what others already recommended in this thread is the simplest solution: Pass the array together with an integer giving the number of elements in the array. Literally all C libraries I have seen do the same, whenever the size of the “array” is needed in a C function.
Note that C strings is another story, those are character arrays but also have a terminating character, which can be used in the Fortran side to compute the size of the char array (length of the string.)

They look similar, but those functions return different values. The fortran size() returns the number of array elements, while c_sizeof() returns the number of bytes. For an integer(10) array, one would return the value 10 while the other would return the value 40 (assuming int32 with byte addressing and that the array size is available, of course).

Is anyone familiar enough with C interop to show how to pass the C array to the fortran assumed shape argument? That may be more complicated than the OP wanted, but it might be good to see how it is done nonetheless.

@actinium226 ,

If you don’t have much leeway on the Fortran side, then you will be limited by nonportable, compiler-specific options (aka processor-dependent) and you will have to follow the RTFM approach and consult your compiler provided “developer and language reference guide”, if available.

Separately, this may be for future reference, know the “canonical” way to interoperate will be using standard facilities - see below:

  • Fortran code: KEEP IT SIMPLE and follow the good coding practices with explicit interfaces (say via MODULEs), use BIND(C) clause, and employ interoperable types, etc. as you have noted.
module m
   use, intrinsic :: iso_c_binding, only : c_int
contains
   subroutine sub( x ) bind(C, name="sub")
      ! Argument list
      integer(c_int), intent(in) :: x(:)
      print *, "In Fortran sub: size(x) = ", size(x)
      print *, x
      return
   end subroutine
end module
  • C code: develop descriptor(s) using standard Fortran stipulated toolsets to more complicated data structures with Fortran-specific attributes; arrays of assumed-shape are one such example, as noted upthread. So the code in C can look like below. Note the bit of added verbosity but which then yields the needed descriptors. The Fortran standard and resources such as Modern Fortran Explained can provide you with more background on the descriptors and methods available to work with them.
#include "ISO_Fortran_binding.h"

extern void sub(CFI_cdesc_t *);

int main(void)
{
   enum N { N = 3 };
   int x[N];
   int i;
   CFI_CDESC_T(1) descriptor_to_x;
   CFI_index_t ext[1];

   for (int i=0; i<N; i++) x[i] = 42+i;

   ext[0] = (CFI_index_t)N;
   i = CFI_establish((CFI_cdesc_t *)&descriptor_to_x, x, CFI_attribute_other,
                      CFI_type_int, 0, (CFI_rank_t)1, ext);

   sub((CFI_cdesc_t *)&descriptor_to_x );
   return 0;
}
  • Building and running such code using Intel Fortran and Microsoft C/C++ toolsets on Windows
C:\temp>ifort /free /standard-semantics -c m.f
Intel(R) Fortran Intel(R) 64 Compiler Classic for applications running on Intel(R) 64, Version 2021.9.0 Build 20230302_000000
Copyright (C) 1985-2023 Intel Corporation.  All rights reserved.


C:\temp>cl /c /W3 /EHsc c.c
Microsoft (R) C/C++ Optimizing Compiler Version 19.34.31937 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

c.c

C:\temp>link c.obj m.obj /subsystem:console /out:c.exe
Microsoft (R) Incremental Linker Version 14.34.31937.0
Copyright (C) Microsoft Corporation.  All rights reserved.


C:\temp>c.exe
 In Fortran sub: size(x) =  3
 42 43 44

You can review a similar example but which involves an ALLOCATABLE received argument on the Fortran side here.

4 Likes

I know, but I was answering a post that was suggesting that size_of() could be a workaround.

Thanks for all the feedback, particularly @Arjen. I think that method of wrapping the fortran call with something that can accept the array and size and create the appropriate fortran type makes the most sense.

I would think that would be more portable between different compilers as well? The idea of using ISO_Fortran_bindings.h to create fortran types on the C side is appealing as well, but I wonder if that would have any issues between different compilers or is the standard for array descriptors consistent across compilers?

I’m not sure that it would make much of a difference for my project since I’m looking to reimplement the fortran code in c and not leave any fortran around, but useful to know and maybe helpful for intermediate steps.

Yes yes, eliminate Fortran in favor of C! It’s very trendy nowadays. I would call this a masochistic act, but whatever.

1 Like

Different fortran compilers store the array metadata in different ways, so there is no portability among fortran object files. The C interoperability features are defined to work between a given pair of fortran and C processors.

You will find that fortran has many high-level concepts that do not transfer directly to C. Assumed shape arrays are one of those. You will need to reinvent and reimplement these concepts in C. I doubt that you will find any enthusiastic support for your task here in a fortran discussion group.

1 Like

Actually, LFortran can translate Fortran codes to C, so once we get to the point that we can compile the author’s code, we would be happy to help.

1 Like

@certik do you have any guides for translating Fortran codes to C?

Once LFortran can compile your code, you just use the --show-c option and it gives you back an equivalent C code. Right now my main focus is on compiling codes, so it’s not ready for production yet.