1

I understand that extra care needs to be taken when allocating memory in C to ensure a 2d array is contiguous, but I still don't get expected outcomes when I pass it to Fortran. Below is a toy version of my attempt: A main.c file which allocates memory for a 2d array and assigns a value to each element, and a foo.f90 file that prints out elements of the 2d array.

#include <stdio.h>
#include <stdlib.h>

void foo_(double **,int *,int *);

int main() {
    int i, j, cols, rows;
    double *_x, **x;

    cols = 3;
    rows = 2;

//  Allocate memory
    _x = malloc(rows*cols*sizeof(double));
    x = malloc(cols*sizeof(double *));
    for(i=0;i<cols;i++)
        x[i] = &(_x[rows*i]);

//  Generate elements for the 2d array 
    for(i=0;i<cols;i++)
    for(j=0;j<rows;j++)
        x[i][j] = i*j;

//  Call Fortran subroutine foo
    foo_(x,&rows,&cols);

    return EXIT_SUCCESS;
}

foo.h

subroutine foo(x,rows,cols)
    use iso_c_binding
    implicit none

    integer(c_long), intent(in)                :: rows,cols
    real(c_double), intent(in), dimension(rows,cols) :: x

    integer :: i,j

    do i = 1,cols
    do j = 1,rows
        print *, j,i,x(j,i)
    end do
    end do

end subroutine

As output, I expect a list of array elements. Instead, I get the following output

           1           1   1.1654415706619996E-316
           2           1   1.1654423611670330E-316
Segmentation fault (core dumped)
Community
  • 1
  • 1
DJames
  • 571
  • 3
  • 17
  • There is **no** 2D array in your C code and nothing which can represent or point to one! A pointer is not an array. If you need a 2D array, use one! – too honest for this site Dec 09 '16 at 12:00
  • 1
    Double pointer can represent 2D array and actually is there @Olaf, I think the problem is in foo_(double **,int *,int *); shouldn't it be just foo_(double **,int,int); ? – koper89 Dec 09 '16 at 12:11
  • @Olaf Your comment is either wrong, or obtuse enough to be didactically useless. In the C programming language, multidimensional arrays stored in the heap have their memory reserved at runtime, and pointers are used as bookkeeping tools for managing addresses ( https://www.cs.swarthmore.edu/~newhall/unixhelp/C_arrays.html ). – DJames Dec 09 '16 at 12:18
  • @francescalus A good point. I have edited the question to include output. – DJames Dec 09 '16 at 12:21
  • @ThomasKelly: 1) The C standard does not enforce using a specific memory management model for dynamic memory allocation. 2) Your assertion is completely wrong. A jagged array is **not** the correct way to use a 2D array. It is a completely different datastructure! You can allocate a 2D array without complication in C. You might want to update to the 17 year old C99, or - better - to the C standard which is C11 (also already 5 years old). Whoever told you about using `double **` is teaching ancient C. 3) This can be one reason for your problem. – too honest for this site Dec 09 '16 at 12:23
  • @Olaf Could you be more specific about what is incorrect about my assertion? I understand that a 2d array can be allocated without complication in C. I am not asserting that a 2d array can only be allocated with complications. I am instead interested in the dynamic allocation of contiguous memory for a 2d array, and the passing of that array to a Fortran subroutine, and it is my understanding that the usual method of dynamic allocation will not ensure contiguity. [edit]-Ok, you have edited your comment to be more didacting. That is appreciated. – DJames Dec 09 '16 at 12:34
  • @Olaf Could you provide a short example of uncomplicated contiguous memory allocation for a 2d array (the size of which is not known at compile time), or provide a link to the relevant C11 standard method? – DJames Dec 09 '16 at 12:42
  • @ThomasKelly: `malloc(siof(double) * cols * rows)` - I'll leave figuring out the declaration for the **single** (not an array!) pointer to you. Do some research, that has been asked and answered here a hundred times. – too honest for this site Dec 09 '16 at 13:32
  • @Olaf malloc(sizeof(double) * cols * rows) is already in the toy code I presented. It looks like you have simply not read my question, which is a very curious thing to do. – DJames Dec 09 '16 at 13:50
  • Read my comment **carefully** again! You asked how to **allocate** the array, not how to handle it (still I gave you a hint)! I don't have the time for this, do research! – too honest for this site Dec 09 '16 at 13:56
  • @Olaf You have made another very peculiar statement. 1) You have told me to "do research", despite the fact that I have indeed done research, as evidenced by my link in the question, and my link in my first comment. 2) I asked about "Attempting to pass a contiguous dynamic 2d array from C to Fortran" and received two very helpful answers, yet you claim I did not actually ask this. There is not much I can say in response until your comments make contact with the question at hand. – DJames Dec 09 '16 at 14:05
  • You are using the intrinsic module (presumably, unless you've written your own) ISO_C_BINDING, which is a Fortran 2003 language feature, but you are not using the Fortran 2003 BIND(C) suffix on the Fortran procedure. This suffix is critical to standard conforming/portable Fortran-C interoperability. Is there a particular reason for this, or is it just an oversight? – IanH Dec 09 '16 at 19:29

2 Answers2

1

You said so yourself: contiguous!

You do not allocate a contiguous array. To allocate one you must write:

//  Allocate memory
double *x = malloc(rows*cols*sizeof(double));

Unfortunately, you must now write the following in C to index x:

//  Generate elements for the 2d array 
for(i=0;i<cols;i++)
    for(j=0;j<rows;j++)
        *(x+j*cols+i) = i*j;

This assumes the matrix is in row-major order (row after row after row laid out contiguous in memory).

Note: in C99 there are Variable Length Arrays where the compiler manages x[i][j] properly. I do not have C99, but maybe another user can give an answer with VLAs.

Paul Ogilvie
  • 25,048
  • 4
  • 23
  • 41
  • That's a type fault. `x` is `double **` and your index is wrong. And why not use a 2D array? It all depends on what the Fortran ABI expects. – too honest for this site Dec 09 '16 at 14:16
  • The question is tagged C, which would be standard C which is C11. While C11 made VLAs optional, no modern C11 compliant compiler does not support them (making an existing language feature optional was a major break with the commitees practice of maintaining downwards-compatibility, likely for maintainers of outdated compilers to better sell them). – too honest for this site Dec 09 '16 at 15:52
0

foo() on the Fortran side receives x as an explicit-shape array, so we need to pass the address of the first element of x. So, we modify the prototype as

void foo_( double *, int *, int * );

and pass the address of the first element as

foo_( _x, &rows, &cols );
// or
// foo_( &( _x[0] ), &rows, &cols );
// or
// foo_( &( x[0][0] ), &rows, &cols );

Also, we probably need to use integer(c_int) rather than integer(c_long) to match int on the C side, such that

integer(c_int), intent(in) :: rows,cols

(On my computer, use of integer(c_long) gives segmentation fault because rows and cols are not passed correctly.)

Further, to make it easy to check the correspondence of array elements, I modified the test values as

for(i=0;i<cols;i++)
for(j=0;j<rows;j++)
    x[i][j] = (i+1) + 1000*(j+1);

and inserted print statements into the Fortran code as

print *, "rows = ", rows
print *, "cols = ", cols

Then, GCC-6 on my computer (OSX 10.9) gives

 rows =            2
 cols =            3
           1           1   1001.0000000000000     
           2           1   2001.0000000000000     
           1           2   1002.0000000000000     
           2           2   2002.0000000000000     
           1           3   1003.0000000000000     
           2           3   2003.0000000000000

As a side note, the following also seems to work (rather than creating x manually):

_x = malloc( rows * cols * sizeof(double) );

typedef double (*Array)[ rows ];
Array x = (Array) _x;

// set x[i][j] the same way

foo_( _x, &rows, &cols );
// or
// foo_( &( x[0][0] ), &rows, &cols );

but I'm not very sure whether this use of Array is standard-conforming... (in C).


[ EDIT ]

Using modern C, it seems possible to declare x directly as a rectangular array with dynamic size with memory allocated on the heap, such that:

double (* x)[rows] = malloc( cols * rows * sizeof(double) );

// or
// double (* x)[rows] = malloc( sizeof( double[cols][rows] ) );

(Please note that the variable names "cols" and "rows" refer to those on the Fortran side, so they appear counter-intuitive on the C side.)

With this x, we can proceed the same way as above to call foo_(), i.e.,

// set x[i][j] via loops

foo_( &( x[0][0] ), &rows, &cols );

Please see the Jens Gustedts' blog ("Don’t use fake matrices") for full details of the latter approach (and thanks to @Olaf for suggestions).

roygvib
  • 7,218
  • 2
  • 19
  • 36
  • It might be true Fortran ABI uses 1D arrays for interfacing to C code, but that should be verified first. After all, this still si something very different from the jagged array OP uses and not a 2D array either. As it has the same (considering correct row/col order) layout, it should work, but still it has a different type and can result in subtle errors, resp. complications and require otherwise unnecessary casts.. – too honest for this site Dec 09 '16 at 14:09
  • OP uses a pointer to pointer, resp. an array of pointers. Which commonly is called "jagged array", no matter of the second-level arrays have the same size or not. The fact that he allocates the sub-arrays en block does not change this. The correct way in C-only would be to use a 2D array, but as I wrote, I'm not sure what Fortran expects. – too honest for this site Dec 09 '16 at 14:13
  • @Olaf The first-level array (_x) is linear and contiguous, correct? Then, my understanding is that x (= second-level array) provides a rectangular access to the memory pointed by _x. What is passed to Fortran is the address allocated by malloc(), which is linear (I assume). Also, my understanding is "jagged array" is that each row or column is allocated independently, so the memory is not contiguous. – roygvib Dec 09 '16 at 14:25
  • But because OP uses the variable names "rows" and "cols" in a bit confusing way (to match Fortran column-major and avoid transpose), the code was not very readable to me at first sight (sorry, just my opinion...) – roygvib Dec 09 '16 at 14:27
  • `_x` is a 1D array. While you can - of course - manually emulate 2D addressing, it is better to use a 2D array. Jagged arrays like in the question are typically used by inexperienced users and seniour who never made the stap to modern C (started with C99). Your array is a 1D array. Not clear what you mean with "first level array. OP's "first level array" is the array of pointers (referenced by a pointer to the first entry). This **is** a jagged array! Maybe you want to look up techniques. ... – too honest for this site Dec 09 '16 at 16:00
  • ... I recommend reading the standard and do some research here what an array compromises and how to construct multidimensional arrays (i.e. "arrays of arrays, not "array of pointers to (the first element of an array". These are very different constructs. – too honest for this site Dec 09 '16 at 16:00
  • I'm now studying rectangular arrays in C, and is this second example in this page the right direction? https://gustedt.wordpress.com/2014/09/08/dont-use-fake-matrices/ – roygvib Dec 09 '16 at 21:22
  • 1
    Yes. Jens is one of the members here, which can generally be trusted with C standard questions. Didn't know he blogs, too. This post is well justified, although C99 is now 17 years old. – too honest for this site Dec 09 '16 at 21:30
  • Thanks very much, the answer updated. This syntax seems very compact and useful (and also more readable). – roygvib Dec 09 '16 at 22:31
  • To emphasize once more: Use the type the Fortran interface expects. For example some well-established math-libraries use 1D arrays and emulate the 2D addressing. Reasons are 1) they were developed before C99 (some even before the C standard) and 2) a still widely used C compiler from a Redmond company refuses to follow the standard since 17 years now. This is a major reason people teach ancient C and even senior developers don't know about VLAs. – too honest for this site Dec 10 '16 at 14:23