2

I'm trying to modify this code (gist as back up) to become gfortran-gcc compatible.

  • I removed the [VALUE] tags
  • used POINTER with -fcray-pointer flag for gfortran, instead of the [REFERENCE] tag
  • removed the __stdcall , because tried the #define __stdcall __attribute__((stdcall)) which caused warning: ‘stdcall’ attribute ignored [-Wattributes] now this is what I have:

C code CMAIN.C:

#include <stdio.h>

extern int FACT_(int n);
extern void PYTHAGORAS_(float a, float b, float *c);

main()
{
    float c;
    printf("Factorial of 7 is: %d\n", FACT_(7));
    PYTHAGORAS_(30, 40, &c);
    printf("Hypotenuse if sides 30, 40 is: %f\n", c);
}

the FORTRAN code FORSUBS.FOR:

  INTEGER*4 FUNCTION Fact (n)
  INTEGER*4 n 
  INTEGER*4 i, amt
  amt = 1
  DO i = 1, n
    amt = amt * i
  END DO
  Fact = amt
  END

  SUBROUTINE Pythagoras (a, b, cp)
  REAL*4 a
  REAL*4 b
  POINTER (cp, c)
  REAL*4 c
  c = SQRT (a * a + b * b)
  END

the Makefile:

all:
    gfortran -c FORSUBS.FOR -fcray-pointer
    gcc -c CMAIN.C
    gfortran -o result.out FORSUBS.o CMAIN.o  
    rm -rf *.o

clean :
    rm -rf *.out *~ *.bak *.o

However I still get the error:

CMAIN.o: In function `main':

CMAIN.C:(.text+0x1d): undefined reference to `FACT_(int)'

CMAIN.C:(.text+0x4c): undefined reference to `PYTHAGORAS_(float, float, float*)'

I would appreciate if you could help me know:

  • What is the issue and how can I resolve it?
  • what is the best method to modify the original code to become gcc-gfortran compatible with minimum change.

P.S.1. also shared on Reddit. P.S.2. operating system and compiler specifications are same as this question.

Community
  • 1
  • 1
Foad S. Farimani
  • 12,396
  • 15
  • 78
  • 193
  • I really don't see any reason for the Cray pointers here. – Vladimir F Героям слава Dec 02 '18 at 23:33
  • @VladimirF any better method to pass a pointer from the C side? – Foad S. Farimani Dec 02 '18 at 23:35
  • You just need to pass the variable by referrence, not to pass a pointer. That is the default Fortran way and it is shown in any tutorial about old Fortran and C. – Vladimir F Героям слава Dec 02 '18 at 23:36
  • 1
    When I compile your `fortran`, and use `nm` on the `.o`, the symbols I get are: `fact_` and `pythagoras_` vs what you've declared in your `.c` file (i.e. they are _lower_ case vs. the _upper_ case you use in your `.c` file) – Craig Estey Dec 02 '18 at 23:36
  • 2
    The name mangling is compiler specific. That's why we have [tag:fortran-iso-c-binding] in this century. There are tons of similar question here. – Vladimir F Героям слава Dec 02 '18 at 23:37
  • @VladimirF I'm using `gfortran` just as OP is. And, he can repeat the same test to see what should be specified. – Craig Estey Dec 02 '18 at 23:39
  • @CraigEstey no idea what "use nm on the .o, the symbols" means, would you please elaborate? I just copy and pasted the error in my Ubuntu terminal. – Foad S. Farimani Dec 02 '18 at 23:40
  • @VladimirF I'm aware of the iso c binding. but I'm collecting all the available examples for FORTRAN 77-90 and C mixed programmings for my repository [here](https://github.com/Foadsf/Cmathtuts/tree/dev). – Foad S. Farimani Dec 02 '18 at 23:42
  • You wil find loads of older examples under that tag as well. Really, your code in the question is very basic and these technieques were discussed here many times. – Vladimir F Героям слава Dec 02 '18 at 23:46
  • @VladimirF would you mind providing link to a similar example? – Foad S. Farimani Dec 02 '18 at 23:48
  • BTW if you are willing ro use such a non-standard thing as Cray pointers, you could well use iso-c-binding as an extension to older Fortran as well. It cerrainly has better future in porrability. – Vladimir F Героям слава Dec 02 '18 at 23:49
  • The tag is full of such examples, but you can just also just read my answer when I took the time to write it.. – Vladimir F Героям слава Dec 02 '18 at 23:50
  • @VladimirF I had no idea that `Cray pointers` are non-standard. I just googled fortran pointer and this was what I found. I write C but my knowledge of FORTRAN is still very primitive. what is the standard solution then? – Foad S. Farimani Dec 02 '18 at 23:51
  • 1
    To pass by reference as I wrote at least twice. Just remove the unneeded `[REFERENCE]`. You should be more worried about the pass by value. – Vladimir F Героям слава Dec 02 '18 at 23:55
  • 1
    @Foad, it is very clear that your knowledge of Fortran is still very primitive as the name of the language is Fortran. It's been Fortran for 3 decades or so. – evets Dec 03 '18 at 00:00
  • 1
    You have to remove `rm -rf *.o` from your `all:` target to retain your `.o` files. Doing `nm FORSUBS.o CMAIN.o` will output the symbol tables of each `.o`. `FORSUBS.o` will have `T` [text] symbols and `CMAIN.o` will show `U` type symbols. These should matchup. – Craig Estey Dec 03 '18 at 00:14
  • It's not good to count on the legacy non-standard integer*4 from before the C era working with C. Better to use the C interoperable types. VALUE in Windows Fortran (at least in a well-known implementation, not necessarily in gfortran) is not the same as iso_c_binding value attribute. In that case, the former would still match C pass by reference; it simply would isolate caller from any attempt by the callee to return data to caller.. – tim18 Dec 03 '18 at 09:12
  • @evets please correct me if I'm wrong but the versions prior to 95 including 66, 77 and 90 are spelled with capital letters. aren't they? – Foad S. Farimani Dec 03 '18 at 10:28

2 Answers2

4

Fortran passes variables by reference, as mentioned in the first sentences of any Fortran to C tutorial. So you:

  1. Cannot just remove the [VALUE], you should add the modern VALUE attribute or change the C code to pass pointers.

  2. Should not use pointers on the Fortran side of [REFERENCE], but just remove it.

  3. Use the actual name mangling for your compiler, these days normally subroutine_name_ (appended underscore, name is lowercase) or use the modern bind(C, name="binding_name") attribute.

Without modern Fortran, but with VALUE (it is just a PITA without):

  INTEGER*4 FUNCTION Fact (n)
  INTEGER*4, VALUE :: n 
  INTEGER*4 i, amt
  amt = 1
  DO i = 1, n
    amt = amt * i
  END DO
  Fact = amt
  END

  SUBROUTINE Pythagoras (a, b, c) bind(C)
  REAL*4, VALUE :: a
  REAL*4, VALUE :: b
  REAL*4 c
  c = SQRT (a * a + b * b)
  END

and than just change your C names to lowercase (pythagoras_, fact_)... With the VALUE attribute you do not need to introduce all those C temporaries you see in the other answer. The bind(C) is required for VALUE to work properly. It will save you from rewriting the code that calls the Fortran procedures.

For best modern experience use bind(C,name="any_name_you_want") to set the exact linkage symbol name.

  • "change the C code to pass pointers" so should I put `7`, `40` and `30` in some variables and then pass the pointer? – Foad S. Farimani Dec 02 '18 at 23:53
  • 1
    See the edit. It takes time to code on a mobile phone. – Vladimir F Героям слава Dec 03 '18 at 00:00
  • @Vladimir, the Fortran does not specify how variables are passed. It my be reference or by value or by chiseling on stone tablets. – evets Dec 03 '18 at 16:04
  • @evets I know that very well and I know even many more details. However, one must distinguish the level of information and the level of correctness based on the target audience. Teachers always lie. Or well... make simplifications. Without this particular simplifications many C codes that call Fortran external subroutines (including very often used packages like BLAS or LAPACK) wouldn't work. Or the Fortran code would have to be updated which is explicitly what this question tries to avoid - it wants **minimal** changes. – Vladimir F Героям слава Dec 03 '18 at 16:19
  • @evets I know here often who writes more gets more upvotes, but to actually inform someone to get the job done less is often better. Even if that means omission of some details. – Vladimir F Героям слава Dec 03 '18 at 16:25
  • @VladimirF I appreciate your support. I will do more research about the `, VALUE ::` attribute. Thanks for your help. – Foad S. Farimani Dec 03 '18 at 16:42
3

In addition to my top comments, Fortran passes by reference, so you have to modify the .c and the .for files.

The code below works. There may be a simpler way to declare things, but this should [at least] get you further along. Caveat: I haven't done much fortran since Fortran IV days, so I'm a bit rusty. I'd defer to Vladimir's VALUE solution as a better way to go.


#include <stdio.h>

#if 0
extern int fact_(int n);
extern void pythagoras_(float a, float b, float *c);
#else
extern int fact_(int *n);
extern void pythagoras_(float *a, float *b, float *c);
#endif

int
main(void)
{
    float c;
#if 0
    printf("Factorial of 7 is: %d\n", fact_(7));
#else
    int n = 7;
    printf("Factorial of 7 is: %d\n", fact_(&n));
#endif

#if 0
    pythagoras_(30, 40, &c);
#else
    float a = 30;
    float b = 40;
    pythagoras_(&a, &b, &c);
#endif
    printf("Hypotenuse if sides 30, 40 is: %f\n", c);

    return 0;
}

    INTEGER*4 FUNCTION Fact (n)
    INTEGER*4 n
    INTEGER*4 i, amt
    amt = 1
    DO i = 1, n
        amt = amt * i
    END DO
    Fact = amt
    END

    SUBROUTINE Pythagoras (a, b, c)
    REAL*4 a
    REAL*4 b
    REAL*4 c
    c = SQRT (a * a + b * b)
    END

Here is the program output:

Factorial of 7 is: 5040
Hypotenuse if sides 30, 40 is: 50.000000

UPDATE:

I get the same undefined reference error from your code!

Aha!

One thing I didn't mention [because I didn't think it would make a difference] is that I changed the source file names to use all lowercase letters (e.g. CMAIN.C --> cmain.c and FORSUBS.FOR --> forsubs.for)

With that, the output of nm *.o produces:

 cmain.o:
                 U fact_
0000000000000000 T main
                 U printf
                 U pythagoras_

forsubs.o:
0000000000000000 T fact_
0000000000000045 T pythagoras_

The change in the fortran source filename doesn't matter too much. But, the C source filename does!

More to the point it's the filename suffix (i.e. .C changed to .c).

This is because gcc will [try to be smart and] look at the suffix to determine which language the file is written in and compile accordingly. For example, gcc -c foo.cpp will compile the file as if it is written in c++ and not c, just as if the command were: g++ -c foo.cpp

Although .cpp is the [more] usual suffix for a c++ filename, an alternate suffix for c++ files is: .C

That is, most projects use the .cpp convention, but some use the .C convention. One of the reasons for preferring .cpp over .C is that Windows filesystems are case insensitive. So, .C and .c would appear the same. However, POSIX systems (e.g. linux, macOSX, iOS, android, etc.) have case sensitive filenames so either convention can be used.

So, gcc -c CMAIN.C will compile as c++. This does c++ style "name mangling" of symbols--not what we want. In c++, the mangling is done to allow "overloading" of function names. That is, two [or more] different functions can have the same name, as long as they use different arguments. For example:

void calc(int val);
void calc(int val1,int val2);
void calc(double fval);

Here is the output of nm *.o if we use CMAIN.C:

 CMAIN.o:
0000000000000000 T main
                 U printf
                 U _Z11pythagoras_PfS_S_
                 U _Z5fact_Pi

FORSUBS.o:
0000000000000000 T fact_
0000000000000045 T pythagoras_

Running that file through c++filt to "demangle" the c++ names we get:

 CMAIN.o:
0000000000000000 T main
                 U printf
                 U pythagoras_(float*, float*, float*)
                 U fact_(int*)

FORSUBS.o:
0000000000000000 T fact_
0000000000000045 T pythagoras_

So, try to use lowercase filenames if possible [that is recommended best practice]. But, at a minimum, don't use .C

Craig Estey
  • 30,627
  • 4
  • 24
  • 48