From mboxrd@z Thu Jan 1 00:00:00 1970 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on polar.synack.me X-Spam-Level: X-Spam-Status: No, score=-0.8 required=5.0 tests=BAYES_00,INVALID_DATE autolearn=no autolearn_force=no version=3.4.4 Path: utzoo!utgpu!news-server.csri.toronto.edu!mailrus!cs.utexas.edu!usc!ucsd!ucbvax!IBM.COM!NCOHEN From: NCOHEN@IBM.COM ("Norman H. Cohen") Newsgroups: comp.lang.ada Subject: aliases/function return values Message-ID: <9008301534.AA11465@ajpo.sei.cmu.edu> Date: 30 Aug 90 13:29:18 GMT Sender: daemon@ucbvax.BERKELEY.EDU Organization: The Internet List-Id: Jerry Callen presents a program that fails if the statement return Global; is implemented by returning a REFERENCE to Global, rather than a COPY of Global, to the caller. He then writes: >I would LIKE to think that objects may be returned by reference; >otherwise potentially large objects would have to be copied. >I would call the program above erroneous, since it is dependent upon >the mechanism used for parameter passing and value return. David Rosenfeld, referring to a compiler that "returns by reference," responds: >It's a compiler bug, but a useful one, and there are no ACVC's which >test for it; so it's probably best to assume that the program >is erroneous. Hold on there! It is not up to the programmer or the implementer to decide what will be considered erroneous. Dependence on the PARAMETER- passing mechanism is erroneous because the reference manual explicitly states (6.2(7)): "For a parameter whose type is an array, record, or task type, an implementation may likewise achieve the above effects by copy, as for scalar types.... Alternatively, an implementation may achieve these effects by reference.... The execution of a program is erroneous if its effect depends on which mechanism is selected by the implementation." No such choice is given for passing function results back to the caller. Indeed, RM 5.8(6) specifically states, "For the execution of a return statement, the expression (if any) is first evaluated," and 5.8(5) states, "The value of the expression determines the result returned by the function." The offenses constituting erroneous execution are specifically enumerated in the RM (with a few more added by AIs). Dependence on the result-passing mechanism is not such an offense. Switching my perspective from language legalities to implementation practicalities, the implementation approach suggested by Callen will very rarely be useful. Most of the time, the expression in a return statement does not name a global variable, but a variable local to the function body, or some expression such as a catenation or aggregate. A reference to a local variable is a pointer into the stack frame that is about to disappear. Indeed, in a context like if Array_Func_1(X) = Array_Func_2(X) then ... the stack frame will likely be overwritten by the second function call before the value of the first function call is dereferenced. When the function result subtype is CONSTRAINED, the caller can determine an appropriate destination for the function result and pass a pointer to that destination into the function body. The return statement is then implemented by copying the result value into the specified destination. In a context like A := Array_Func(X); the caller can pass the address of A, so that both the return and the assignment statement are achieved with one copy of the array value. In a context like Proc(Array_Func(X)); the caller can pass an address in the stack frame it is setting up for the call on Proc, or the address of a temporary whose address will be placed in that stack frame. Usually, the only useful context for a call on a function with an UNCONSTRAINED array result is as a parameter to some outer subprogram call--as in Text_IO.Put(Text_IO.Name(Input_File)); --or as the initial value of an allocator--as in String_Pointer := new String'(Text_IO.Name(Input_File)); --or as PART of the initial value for an allocator for some record type with discriminants--as in String_List := new List_Node_Type' (Size => Name_Length, Text => Text_IO.Name(Input_File), Link => String_List); In the first case, the implementation can allocate temporary space at the other end of storage, construct the result value there (entailing a copy of each array component value), and return a pointer into that space for the caller to use in passing the array by reference to the outer subprogram call. (It is essential that the caller free the allocated space at the end of the outer call, to prevent storage leaks.) In the second case, the implementation can proceed as above, but allocating space for the function result directly in the collection for the corresponding access type, so that a pointer to the allocated space can be used directly as the value of the allocator. The third case is more complicated. The most reasonable approach is probably for the function to yield a pointer as in the first case and for evaluation of the record aggregate to perform the check that the array pointed to satisfies the index constraint for the corresponding array component, then copy the array yet again, then free the temporary copy constructed by the function. The extra copy can be avoided by passing the address and index constraint of the record component, which is allocated on the basis of discriminant values before the function is called, and proceeding as in the constrained case; however, potential benefits may be outweighed by the cost and complexity of adding an extra implicit parameter to functions with unconstrained array-type results, indicating which return method to use for a given call. Callen bemoans the fact that potentially large objects have to be copied, but returning an array as a function result is inherently a potentially expensive operation, especially if the result subtype is unconstrained. Fortunately, clever compilation can keep the cost reasonable in most cases, while preserving the semantics required by the reference manual. Norman H. Cohen