Intel® Fortran Compiler
Build applications that can scale for the future with optimized code designed for Intel® Xeon® and compatible processors.
Announcements
27659 Discussions

## Fortran 2022 treats value type from C++ as address

New Contributor I
758 Views

Hi,

I am in the process of upgrading a large mixed language (C++/Fortran) solution from Visual Studio & Fortran 2013 to Visual Studio 2022 (17.1.6) and Fortran 2022 (2022.1.0.139).

I have many examples of trivial getter/setter subroutines in Fortran for marshalling data from C++, for example:

IMPLICIT NONE
END

Calling code from C++:

extern "C"
{

}

...

const int numbertopass = 1;

In the 2013 solution this all works fine.

Following the upgrade, the debugger breaks in the Fortran subroutine with a read access violation exception on the line where the assignment occurs.  The watch window reports "Undefined address" on the incoming value, suggesting it may be being erroneously interpreted as an address (0x1) and not a value - which would of course be expected to blow up.

If I change the code to pass by reference, it works fine, for example:

IMPLICIT NONE
END

Calling code:

extern "C"
{

}

...

const int numbertopass = 1;

My Fortran compiler command is similar to:

"C:\Program Files (x86)\Intel\oneAPI\compiler\2022.1.0\windows\bin\intel64_ia32\ifort.exe" /nologo /debug:full /warn:interfaces /iface:cvf /names:lowercase /iface:nomixed_str_len_arg /module:"C:\someproject\int\debug\" /object:"C:\someproject\bin\debug\somecode.obj" /Fd"C:\someproject\int\debug\vc143.pdb" /traceback /check:bounds /check:stack /libs:static /threads /dbglibs /Qvc14.3 /Qlocation,link,"C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Tools\MSVC\14.31.31103\bin\Hostx86\x86" /c C:\someproject\somecode.for

Can anyone shed light on this?  Thanks.

1 Solution
Honored Contributor II
734 Views

See this thread circa 2013 and the comments by @Steve_Lionel regarding changes starting what was then version 14.0 of Intel Fortran compiler:

https://community.intel.com/t5/Intel-Fortran-Compiler/OPTIONAL-and-VALUE-attributes-unexpected-behaviour/td-p/977150

The above might be relevant to you but Intel team and/or @Steve_Lionel may best be able to explain to you the situation re: VALUE attribute in the absence of BIND(C,..) which is what you appear to have in your code.

13 Replies
Honored Contributor II
735 Views

See this thread circa 2013 and the comments by @Steve_Lionel regarding changes starting what was then version 14.0 of Intel Fortran compiler:

https://community.intel.com/t5/Intel-Fortran-Compiler/OPTIONAL-and-VALUE-attributes-unexpected-behaviour/td-p/977150

The above might be relevant to you but Intel team and/or @Steve_Lionel may best be able to explain to you the situation re: VALUE attribute in the absence of BIND(C,..) which is what you appear to have in your code.

New Contributor I
716 Views

Thanks, the link explains the behavior I am seeing here.

Adding BIND(C) has done the trick. I also needed to change the calling convention of the C++ function export to avoid a name decoration (leading underscore) mismatch:

extern "C"
{

}

IMPLICIT NONE
END

I need to now decide which of changing my code everywhere to BIND(C) or using pass by reference would be less disruptive.

Honored Contributor II
707 Views

You could use  bind(C, name =..... )  to avoid changing the call convention maybe. "Integer(C_INT), value" would be nicer also

New Contributor I
700 Views

I don't see how BIND(C, name=...) would avoid changing the calling convention - it forces the use of cdecl doesn't it?  Presumably I would also need  !DIR$ATTRIBUTES STDCALL to go back to stdcall. I agree about the the use of C types. We do that in a few places but not consistently. For now, I've settled on passing const references from the calling side. Seems to work. Honored Contributor II 697 Views if set_somenumber(numbertopass) on the C side expects a decorated name then you can name the routine of the Fortran side if you wanted to I would expect. Maybe I am misunderstanding something. Black Belt Retired Employee 685 Views Re: calling convention Intel Fortran uses the C convention unless you explicitly override it. (On x64, there is only one calling convention.) You can use !DIR$ ATTRIBUTES STDCALL with BIND(C) if needed.

As is said earlier, VALUE without BIND(C) means something different from what you wanted - it passes an anonymous, writable copy of the argument by reference. BIND(C) also prevents hidden arguments and should be used when calling other languages where possible. And if you're using C++, definitely use 'extern "C"' on the C++ side.

For naming, BIND(C) downcases the name and applies whatever name decoration the "companion C processor" would. Note that it doesn't say C++ - if you are using C++ you'll get a mangled name without 'extern "C"'.

New Contributor I
639 Views

Thanks for the info.  I retained __stdcall and changed the C++ callee to accept const references, which works fine.  After discussing with a colleague who has some Fortran experience, I also replaced VALUE with INTENT(IN).

I am now stuck on a possibly related marshalling issue.  As with the problem above, the code runs correctly with Composer 2013.

This time it concerns passing arrays to a C++ routine.  Here is a simplified example:

Fortran:

SUBROUTINE TEST()
IMPLICIT NONE
DOUBLE PRECISION array_1d(3)
DOUBLE PRECISION array_2d(2,2)

array_1d(1) = 1.D0/3.D0
array_1d(2) = 1.D0/7.D0
array_1d(3) = 1.D0/13.D0
array_2d(1,1) = 2.D0/3.D0
array_2d(1,2) = 2.D0/7.D0
array_2d(2,1) = 2.D0/11.D0
array_2d(2,2) = 2.D0/13.D0

CALL LOGARRAY([array_1d], SHAPE(array_1d))
CALL LOGARRAY([array_2d], SHAPE(array_2d))
RETURN
END SUBROUTINE TEST

SUBROUTINE LOGARRAY(elements, shape)
USE ISO_C_BINDING
IMPLICIT NONE
DOUBLE PRECISION, DIMENSION(*), INTENT(IN), TARGET :: elements
INTEGER, INTENT(IN), TARGET :: shape(:)

CALL logcpp(C_LOC(elements), SIZEOF(elements(1)), C_LOC(shape), SIZE(shape))

RETURN
END SUBROUTINE LOGARRAY

C++:

extern "C"

{

void __stdcall logcpp(const char *elements, const int& element_size, const int *shape, const int& rank);

}

void __stdcall logcpp(const char *elements, const int& element_size, const int *shape, const int& rank)

{

if (rank == 0) // Scalar

{
log_data(&elements[0]);  // log shows &elements[0] has wrong value
}

else if (rank == 1) // Rank-1 array

{

int nElem = shape[0];   // shape[0] is pointing to wrong memory (huge number)
for (int i = 0; i < nElem; ++i)
{
log_data(&elements[i*element_size]);
}
}

Thank you.

Black Belt Retired Employee
630 Views

You DO want VALUE (and BIND(C), which I don't see in this example.) You DON'T want C_LOC. INTENT(IN) doesn't change how anything is passed. What you have now is passing an address by reference, an extra set of indirection.

What do you think the square brackets in [array_1d], etc., are doing for you? I suppose it makes this passing an expression rather than a variable, but I don't see the point.

New Contributor I
610 Views

Thanks Steve, you hit the nail on the head with C_LOC.  I don't know what the intention of the square brackets was I'm afraid (I'm not very experienced with Fortran, and the person who wrote this left).

I removed VALUE (example from the original post) because I changed the C++ interface in the trivial setter functions to accept const references.  They seem to work OK.

Here's the working code following your suggestions (key diffs in bold):

Fortran:

SUBROUTINE TEST()
IMPLICIT NONE
DOUBLE PRECISION array_1d(3)
DOUBLE PRECISION array_2d(2,2)

array_1d(1) = 1.D0/3.D0
array_1d(2) = 1.D0/7.D0
array_1d(3) = 1.D0/13.D0
array_2d(1,1) = 2.D0/3.D0
array_2d(1,2) = 2.D0/7.D0
array_2d(2,1) = 2.D0/11.D0
array_2d(2,2) = 2.D0/13.D0

CALL LOGARRAY(array_1d, SHAPE(array_1d))
CALL LOGARRAY(array_2d, SHAPE(array_2d))
RETURN
END SUBROUTINE TEST

SUBROUTINE LOGARRAY(elements, shape)
USE ISO_C_BINDING
IMPLICIT NONE
DOUBLE PRECISION, DIMENSION(*), INTENT(IN), TARGET :: elements
INTEGER, INTENT(IN), TARGET :: shape(:)

INTERFACE
SUBROUTINE Log(elements, sizeOfElement, shape, sizeOfShape) BIND(C,NAME='logcpp')
USE ISO_C_BINDING
DOUBLE PRECISION elements
INTEGER(KIND=C_SIZE_T), VALUE :: sizeOfElement
INTEGER shape
INTEGER, VALUE :: sizeOfShape
END SUBROUTINE Log
END INTERFACE

CALL Log(elements(1), SIZEOF(elements(1)), shape(1), SIZE(shape))

RETURN
END SUBROUTINE LOGARRAY

C++:

extern "C" void __cdecl logcpp(const char* elements, const int elementSize, const int* shape, const int rank);

I actually got this cut-down example working without the interface and BIND(C).  However, my real code also passes a string and a function pointer (C_FUNLOC), and with these extra parameters, suddenly it doesn't like elements(1) and shape(1):

error #6633: The type of the actual argument differs from the type of the dummy argument. [ELEMENTS].

error #6633: The type of the actual argument differs from the type of the dummy argument. [SHAPE].

So perhaps the only way to get this working reliably (esp. when marshalling different C types in the same call) is to use an interface block with BIND(C), even though more trivial examples apparently don't always require this.

Black Belt Retired Employee
596 Views

My understanding is that "const" is like INTENT(IN) and doesn't change how the argument is received. I can't help with the errors because you haven't shown the source that generates them - the code you posted doesn't.  I am a bit puzzled that on the C++ side elements is a char array but you're passing a double-precision array element to it.  What is the intention here?

New Contributor I
586 Views

"My understanding is that "const" is like INTENT(IN) and doesn't change how the argument is received"

Yes, const is simply a compile-time check.

The char array is just a way of allowing Fortran to pass data of any type to the C++, along with a callback to the log routine appropriate to the type.  In a pure C++ app you would just use a method template.  The logcpp function can accept underlying INTEGER, DOUBLE PRECISION, REAL etc. data.

I tried to create a minimal example that reproduces the problem (including the string and function pointer) but it compiles without any problem.  The real code is too big to post but the example has all the key elements so I'm not sure quite what was going on with the error.  In any case, the code is stable with the interface - I'm going to run with that, just pleased to have this working at last!  Thanks again.

Black Belt Retired Employee
583 Views

OK - in the future, you can declare the argument that accepts any type as TYPE(*). This is essentially the same as C's void and will match any type.

New Contributor I
567 Views

Good to know - thanks.