user3245549
user3245549

Reputation: 101

Fortran variable getting overwritten when linked with C function

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 writeFirstStr 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

Answers (2)

ROTA
ROTA

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

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.

(From http://gcc.gnu.org/onlinedocs/gfortran/Argument-passing-conventions.html#Argument-passing-conventions)

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

Related Questions