Reputation: 101
I've added a data collection routine to some legacy fortran. For ease of use, I wrote the file i/o routines in C.
I'm using gcc and gfortran.
Problem: Some fortran variable names are being over written during what appears to be an innocuous call to a C function.
C functions are all type void, names are all lower case, all arguments are pointers, function names all contain a trailing "_", and are called from Fortran as subroutines. I've done this before. gfortran forces all Fortran symbols to lower case, and all entry points have an appended "_" to distinguish from same named C entry points.
Here's a fragment of the C file:
#define MAXFILES 20
FILE *outfile[MAXFILES];
/* int2char_ generates a left zero padded string (*theChar) from an int
(*theInt), that is *numChar characters long. E.g, called from fortran:
string *5 arun
integer nrun
integer nnchar
nrun = 231
nchar = 4
call int2char (nrun, arun, nnchar)
c... returns '0231' in arun
*/
void int2char_ (int *theInt, char *theChar, int *numChar) {
int nchar;
nchar = *numChar;
if (nchar > 9) nchar = 9;
if (nchar < 1) nchar = 1;
sprintf(theChar, "%*.*d", nchar, nchar, *theInt);
return;
} // end of int2char
void openwrite_ (char *filename, int *unit) {
outfile[*unit] = fopen (filename, "w");
return;
} /* end of openWrite */
void closefile_ (int *unit) {
int closed;
if (outfile[*unit]) {
closed = fclose (outfile[*unit]);
}
return;
}
void writefirststr_ (char *string, int *unit) {
int printed;
printed = fprintf (outfile[*unit], "%s", string);
// printed = fputs (string, outfile[*unit]);
return;
}
Here's the declaration of the Fortran variable that's getting stepped on:
c...................
c"Display the mass matrix when DISMAT is set TRUE "
LOGICAL, save :: DISMAT
c...................
Note: I originally used the volatile
declaration qualifier in place of the save
qualifier. No difference.
Here's the call:
c...................
c... build file name
numchar = 4
call int2char (nrun, filenumber, numchar)
begin = 1
end = len_trim(fileprefix)
filename(begin:end) = fileprefix
begin = end + 1
end = begin + 3
filename(begin:end) = filenumber
begin = end + 1
end = begin + 3
filename(begin:end) = fileext
begin = end + 1
filename(begin:begin) = char(0)
c... close open file
call closefile (lunit)
c... open file
call openWrite (filename, lunit)
c... write header(s)
call writeFirstStr (atime', lunit)
c...................
The problem occurs when I execute the call writefirstStr ('time', lunit)
line.
atime
is a character*5
that is datatized to 'time' and explicitly null terminated by: time(5:) = char(0)
. Stepping through writefirststr_()
shows no problems, and the correct information is written to the file.
If I jump (via gdb) to the return statement (in the fortran routine that contains the code fragment above) after the call openWrite (filename, lunit)
, there is no problem.
Calling writeFirstSt
r is what is overwriting the fortran variable DISMAT
. I should also note that DISMAT
is not in the routine making the C language call above.
What I have not yet tried is using the save
qualifier on all Fortran variables - logistical problem due to the amount of legacy code.
Anyone have any thoughts?
Upvotes: 1
Views: 507
Reputation: 111
I think, that there are several problems in your code:
sprintf(theChar, "%*.*d", nchar, nchar, *theInt);
The format string should be "%*d" because you can only pass one integer as precision for an Integer.
The next problem is an bad documented "Feature" of Fortran. Fortran passes the size of a string as hidden argument at the end of all variable (and this time by value and not by pointer).
your function should look like:
void int2char_ (int *theInt, char *theChar, int *numChar, long len_of_the_char);
void writefirststr_ (char *string, int *unit, long len_of_string);
The order of the length variables is in the same as the strings are passed. The data type of the length variable depends on the compiler and the OS. Pay attention in Fortran 90 and explicit interfaces. It may be possible, that an explicit interface suppress this additional argument.
The next problem can occur, if you try to close a unit, without opening before. You do not initialize the global array (or you did not post the code ;-) )
Deleted the rest of this entry (thanks to Vladimir F).
Upvotes: 0
Reputation: 60008
Looks like a problem with the calling conventions to me. You are passing a character variable. Fortran usually uses a hidden variable for the string length.
Your void writefirststr_
doesn't have such a parameter.
For arguments of CHARACTER type, the character length is passed as hidden argument. For deferred-length strings, the value is passed by reference, otherwise by value. The character length has the type INTEGER(kind=4). Note with C binding, CHARACTER(len=1) result variables are returned according to the platform ABI and no hidden length argument is used for dummy arguments; with VALUE, those variables are passed by value.
For any new Fortran code I always recommend to use the modern Fortran interoperability with C (bind(C)
) and the iso_c_binding
intrinsic module.
Upvotes: 2