Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

More experimentation with C-style optional arguments

26 views
Skip to first unread message

James Van Buskirk

unread,
Dec 19, 2007, 11:59:30 PM12/19/07
to
A while back I posted some code that used bind(C) to attempt to
pass arguments to C functions that considered an argument
not present if the pointer to it was NULL. In the ifort docs
it says: "The ATTRIBUTES directive option ALLOW_NULL enables a
corresponding dummy argument to pass a NULL pointer (defined by a
zero or the NULL intrinsic) by value for the argument." The NULL
pointer part seems pretty obvious, but the part about passing
"a zero" needs testing.

C:\gfortran\clf\NULLtest>type NULLtest.f90
program NULLtest
implicit none
integer, parameter :: ik8 = int_ptr_kind()
interface
subroutine sub(x)
import ik8
implicit none
!DEC$ ATTRIBUTES REFERENCE, ALLOW_NULL :: x
integer(ik8) x
end subroutine sub
end interface
integer(ik8), pointer :: p
integer(ik8) x
integer(ik8), parameter :: y = 0

nullify(p)
call sub(p)
call sub(NULL())
allocate(p)
p = -163
call sub(p)
x = -43
call sub(x)
call sub(0)
call sub(0_ik8)
x = 0
call sub(x)
call sub(y)
call sub(transfer(.FALSE._ik8,y))
call sub((y))
call sub((0_ik8))
end program NULLtest

subroutine sub(x)
integer, parameter :: ik8 = int_ptr_kind()
integer(ik8), value :: x
integer(ik8) y
pointer(p, y)

if(x == 0) then
write(*,'(a)') 'In sub, x = NULL'
else
p = x
write(*,'(a,i0)') 'In sub, x = ', y
end if
end subroutine sub
C:\gfortran\clf\NULLtest>ifort NULLtest.f90
Intel(R) Fortran Compiler for Intel(R) EM64T-based applications, Version 9.1
Build 20061104
Copyright (C) 1985-2006 Intel Corporation. All rights reserved.

NULLtest.f90(29) : Warning: The data type of the actual argument does not
match
the definition. [TRANSFER]
call sub(transfer(.FALSE._ik8,y))
------------^
Microsoft (R) Incremental Linker Version 8.00.40310.39
Copyright (C) Microsoft Corporation. All rights reserved.

-out:NULLtest.exe
-subsystem:console
NULLtest.obj

C:\gfortran\clf\NULLtest>NULLtest
In sub, x = NULL
In sub, x = NULL
In sub, x = -163
In sub, x = -43
In sub, x = NULL
In sub, x = NULL
In sub, x = 0
In sub, x = NULL
In sub, x = 0
In sub, x = 0
In sub, x = 0

Now, I don't get that warning about line 29, but it appears that in
this context "a zero" means a literal or named constant zero,
independent of kind, but not a variable with value zero or even an
initialization expression evaluating to zero. Have I got that
right?

I seemed to recall some issue with gfortran and passing nullified
pointers in this context but a direct test didn't yield any of the
problems I remembered:

C:\gfortran\clf\NULLtest>type NULLtest2.f90
program NULLtest2
use ISO_C_BINDING
implicit none
interface
subroutine sub(x) bind(C)
import C_CHAR
implicit none
character(C_CHAR) x(*)
end subroutine sub
end interface
character(8), target :: x
character(kind=C_CHAR), pointer :: ptr(:)
type(C_PTR) cp

x = 'Hello!'//achar(0)
call sub(x)
allocate(ptr(8))
ptr = transfer('Sample'//achar(0),['x'])
! ptr = transfer('Sample'//achar(0),ptr) ! Causes ICE
call sub(ptr)
deallocate(ptr)
call sub(ptr)
cp = C_NULL_PTR
call C_F_POINTER(cp,ptr,[0])
call sub(ptr)
cp = C_LOC(x(1:1))
call C_F_POINTER(cp,ptr,[0])
call sub(ptr)
end program NULLtest2

subroutine sub(x) bind(C)
use ISO_C_BINDING
implicit none
type(C_PTR), value :: x
character(C_CHAR) c
pointer(p, c)

if(C_ASSOCIATED(x)) then
p = transfer(x,p)
write(*,'(a)',advance='no') 'In sub, x = '
do while(c /= achar(0))
write(*,'(a)',advance='no') c
p = p+1
end do
write(*,'()')
else
write(*,'(a)') 'In sub, x = NULL'
end if
end subroutine sub

C:\gfortran\clf\NULLtest>c:\gfortran\win64\bin\x86_64-pc-mingw32-gfortran
NULLte
st2.f90 -fcray-pointer -oNULLtest2

C:\gfortran\clf\NULLtest>NULLtest2
In sub, x = Hello!
In sub, x = Sample
In sub, x = NULL
In sub, x = NULL
In sub, x = Hello!

As can be seen, if the ICE is worked around as noted, everything is
going down smooth. Maybe I'll have to review my earlier code and
see if it has a mistake.

--
write(*,*) transfer((/17.392111325966148d0,6.5794487871554595D-85, &
6.0134700243160014d-154/),(/'x'/)); end


Steve Lionel

unread,
Dec 20, 2007, 8:35:04 AM12/20/07
to
On Dec 19, 11:59 pm, "James Van Buskirk" <not_va...@comcast.net>
wrote:

> Now, I don't get that warning about line 29, but it appears that in
> this context "a zero" means a literal or named constant zero,
> independent of kind, but not a variable with value zero or even an
> initialization expression evaluating to zero. Have I got that
> right?

Yup. The sole intent of this attribute is to allow you to pass the
named constant NULL (or similar) to ease the use of the Win32 API
where that is used to specify an omitted argument (passed by
reference) that could be of any type. It's picky for a reason.

In the early days of DVF, we had to create silly-looking values such
as NULL_SECURITY_ATTRIBUTES if you wanted to omit that argument in a
Win32 call - this was declared as a nullified pointer of the proper
type. By creating the ALLOW_NULL attribute, coding to the Win32 API
was made easier. In general, we use this internally in the provided
Win32 API modules and don't expect customers to use it in their own
code (though it's documented in case you want to.)

Steve

James Van Buskirk

unread,
Dec 21, 2007, 2:55:55 AM12/21/07
to
"Steve Lionel" <steve....@intel.com> wrote in message
news:531f8250-b7ff-44e0...@j20g2000hsi.googlegroups.com...

> On Dec 19, 11:59 pm, "James Van Buskirk" <not_va...@comcast.net>
> wrote:

>> Now, I don't get that warning about line 29, but it appears that in
>> this context "a zero" means a literal or named constant zero,
>> independent of kind, but not a variable with value zero or even an
>> initialization expression evaluating to zero. Have I got that
>> right?

> Yup. The sole intent of this attribute is to allow you to pass the
> named constant NULL (or similar) to ease the use of the Win32 API
> where that is used to specify an omitted argument (passed by
> reference) that could be of any type. It's picky for a reason.

My point was that it didn't seem picky enough to me. The tricky
part happens when the dummy argument is documented as a pointer to
integer and the intent is to provide an integer, not NULL, but the
actual argument is defined through all sorts of !DEC$ IF, INCLUDE,
ans USE so that it's not obvious without looking through several
layers of indirection that the named constant had the value zero.

Then when the infrequently encountered path that invokes the function
actually happens, it won't be at all obvious where things went wrong.
I realize that you're stuck with this feature (of treating literal or
named constant with the special value of zero differently in this
case) but when the invocation would have been standard-conforming
without ALLOW_NULL and the meaning is changed by ALLOW_NULL it
would be less scary to me if a warning were printed out.

> In the early days of DVF, we had to create silly-looking values such
> as NULL_SECURITY_ATTRIBUTES if you wanted to omit that argument in a
> Win32 call - this was declared as a nullified pointer of the proper
> type. By creating the ALLOW_NULL attribute, coding to the Win32 API
> was made easier. In general, we use this internally in the provided
> Win32 API modules and don't expect customers to use it in their own
> code (though it's documented in case you want to.)

Definitely a reasonable extension, with the one worry above.

Now I was able to reconstruct what I didn't like that gfortran did
in a similar situation but with bind(C):

C:\gfortran\clf\NULLtest>type NULLtest3.f90
program NULLtest3


use ISO_C_BINDING
implicit none
interface
subroutine sub(x) bind(C)

use ISO_C_BINDING
implicit none
type(C_PTR) x


end subroutine sub
end interface

type(C_PTR) cp
character(kind=C_CHAR), pointer :: string(:)
type(C_PTR), pointer :: fp
character(*,C_CHAR), parameter :: message = 'Test message'//achar(0)

nullify(fp)
write(*,'(/a)') 'Result of passing nullified pointer to sub'
call sub(fp)
allocate(fp)
cp = C_NULL_PTR
fp = cp
write(*,'(/a)') 'Result of passing pointer to C_NULL_PTR to sub'
call sub(fp)
allocate(string(1))
string = achar(0)
cp = C_LOC(string(1))
fp = cp
write(*,'(/a)') 'Result of passing pointer to empty string to sub'
call sub(fp)
deallocate(string)
allocate(string(len(message)))
! string = transfer(message,string) ! As before
string = transfer(message,[message(1:1)])
cp = C_LOC(string(1))
fp = cp
write(*,'(/a)') 'Result of passing message to sub'
call sub(fp)
write(*,'(/a)') 'Result of passing C_NULL_PTR to sub'
call sub(C_NULL_PTR)
write(*,'(/a)') 'Result of passing NULL() to sub'
call sub(NULL())
end program NULLtest3

subroutine sub(x) bind (C)


use ISO_C_BINDING
implicit none
type(C_PTR), value :: x

type(C_PTR), pointer :: ptr
character(kind=C_CHAR), pointer :: string(:)
integer n

if(C_ASSOCIATED(x)) then
call C_F_POINTER(x, ptr)
if(C_ASSOCIATED(ptr)) then
n = 1
do
call C_F_POINTER(ptr, string, [n])
if(string(n) == achar(0)) exit
n = n+1
end do
if(n == 1) then
write(*,*) 'Sub received pointer to empty string'
else
write(*,*) 'Sub received pointer to: ',string(1:n-1)
end if
else
write(*,*) 'Sub received pointer to NULL'
end if
else
write(*,*) 'Sub received NULL input'


end if
end subroutine sub
C:\gfortran\clf\NULLtest>c:\gfortran\win64\bin\x86_64-pc-mingw32-gfortran
NULLte

st3.f90 -oNULLtest3

C:\gfortran\clf\NULLtest>NULLtest3

Result of passing nullified pointer to sub
Sub received NULL input

Result of passing pointer to C_NULL_PTR to sub
Sub received pointer to NULL

Result of passing pointer to empty string to sub
Sub received pointer to empty string

Result of passing message to sub
Sub received pointer to: Test message

Result of passing C_NULL_PTR to sub
Sub received pointer to NULL

Result of passing NULL() to sub
Sub received pointer to NULL

It's the result of the last test that I don't like: the NULL()
intrinsic should yield a nullified pointer so the result should
be the same as the first test. I'm not sure why it isn't here.

James Van Buskirk

unread,
Dec 22, 2007, 2:34:06 AM12/22/07
to
Didn't know where to put this so I put it here. Here is another
amusing example with the NULL intrinsic:

module stuff
implicit none
contains
subroutine nonsense(p)
real, pointer :: p

write(*,*) associated(p)
end subroutine nonsense
end module stuff

program NULLtest4
use stuff
implicit none
real, pointer :: p

call nonsense(NULL())
call nonsense(NULL(NULL(p)))
end program NULLtest4

And since structure construction is supposed to work 'as if' it were
a bunch of componentwise assignment statements, will this work when
gfortran's handling of mixed integer/logical operations is updated?
Will it be documented?

program mixtest
implicit none
type tl
logical x
end type tl
type ti
integer x
end type ti
logical l
integer i

l = 1
i = .TRUE.
write(*,*) l, i
write(*,*) tl(1)
write(*,*) ti(.TRUE.)
end program mixtest

0 new messages