Rational Developer for System z
PL/I for Windows, Version 8.0, Programming Guide

How data is passed

Both PL/I and C support various methods of passing data. To understand these methods, you must know the following terms:

Parameter
A variable declared in a PL/I procedure or function definition. For example, seed is a parameter in the following PL/I function definition.
  funky:
     proc( seed )
     returns( fixed bin(31) );

     dcl seed fixed bin(31);
         .
         .
         .
  end funky;
Argument
A variable or value actually passed to a routine. When the function funky (from the preceding example) is invoked by rc = funky( seed );, seed is an argument.
By value
The value of the argument is passed. When a calling routine passes an argument by value, the called routine cannot alter the original argument.
By address
The address of the argument is passed. When a calling routine passes an argument by address, the called routine can alter the caller's argument.

C passes all parameters by value, but PL/I (by default) passes parameters by address. PL/I also supports passing parameters by value except for arrays, structures, unions, and strings with length declared as *.

As is described in more detail in the PL/I Language Reference, you can indicate if a parameter is passed by address or by value by declaring it with the BYADDR or BYVALUE attribute. In the following example, the first parameter to modf is passed by value, while the second is passed by address.

  dcl modf entry( float bin(53) byvalue,
                  float bin(53) byaddr )
           returns( float bin(53) );

The corresponding C declaration is:

  double modf( double x, double * intptr );

If the BYADDR or BYVALUE attributes are not explicit in the declaration, you can specify them in the options list for that entry. The following declare uses the options list making it equivalent to the previous example.

  dcl modf entry( float bin(53),
                  float bin(53) byaddr )
           returns( float bin(53) )
           options( byvalue );

Even when a parameter is passed by address, its value might not be changed by the receiving routine. You can indicate this in PL/I by adding the attribute NONASSIGNABLE (or NONASGN) to the declaration for that parameter. The following partial declaration indicates that neither of the arguments to the function strcspn is altered by that function:

  dcl strcspn entry( nonasgn char(*) varyingz,
                     nonasgn char(*) varyingz )
              returns( fixed bin(31) );

The corresponding C declaration is:

  int strcspn( const char * string1, const char * string2 );

A routine must agree with any routines that call it about how data is passed between them. You can avoid potential problems by giving the compiler enough information to detect these kinds of mismatches. For example, while the following declare is technically equivalent to the declare for modf in the sample code shown earlier, it allows the address of any argument to be passed as the second argument. The earlier declares would require the second argument to have the correct type.

  dcl modf entry( float bin(53),
                  pointer )
           returns( float bin(53) )
           options( byvalue );

Finally, when PL/I passes some data types (strings, arrays, structures, and unions), it also, by default, passes a descriptor that describes data extents (maximum string length, array bounds, etc.). Since C routines cannot consume PL/I descriptors, you should keep descriptors from being passed between C and PL/I routines. You can do this by adding the NODESCRIPTOR option to the OPTIONS attribute in the declaration for the C entry, for example:

  dcl strcspn entry( nonasgn byaddr char(*) varyingz,
                     nonasgn byaddr char(*) varyingz )
              returns( fixed bin(31) )
              options( nodescriptor );
Rational Developer for System z
PL/I for Windows, Version 8.0, Programming Guide