Hi GPC Crew,
I just hit something I do not really understand in the way GPC passes parameters to procedures. The following sample program:
Program ParamTest; Var S40 : String(40); I : Integer;
Procedure TstParams(S : String; IL : Integer); Begin WriteLn('Before: S=', S, ' IL=', IL); S := S + 'BBB'; { Using Concat() does the same } IL := IL + 4; WriteLn('After: S=', S, ' IL=', IL); End;
Begin S40 := 'AAA'; I := 4; TstParams(S40, I); End.
Produces the following output: Before: S=AAA IL=4 After: S=AAA IL=8
How come the string cannot be modified inside of the procedure (without impacting the global string) but the integer can? Until now, my understanding of the parameters passing mechanism was that when you pass a parameter by value, the procedure uses a local copy of it, and this copy may be modified locally. This mechanism seems to work for integers but not for strings! However, if the string modification is somewhat "illegal" at least GPC could issue a compilation warning.
I would be interested to understand better how this works, because this behavior causes bugs in the programs I am porting from SUN Pascal and perhaps in ways I don't see as clearly as in this example.
Thanks
Pascal Viandier pascal@accovia.com
Pascal Viandier wrote:
I just hit something I do not really understand in the way GPC passes parameters to procedures. The following sample program:
Program ParamTest; Var S40 : String(40); I : Integer;
Procedure TstParams(S : String; IL : Integer); Begin WriteLn('Before: S=', S, ' IL=', IL); S := S + 'BBB'; { Using Concat() does the same } IL := IL + 4; WriteLn('After: S=', S, ' IL=', IL); End;
Begin S40 := 'AAA'; I := 4; TstParams(S40, I); End.
Produces the following output: Before: S=AAA IL=4 After: S=AAA IL=8
How come the string cannot be modified inside of the procedure (without impacting the global string) but the integer can? Until now, my understanding of the parameters passing mechanism was that when you pass a parameter by value, the procedure uses a local copy of it, and this copy may be modified locally. This mechanism seems to work for integers but not for strings! However, if the string modification is somewhat "illegal" at least GPC could issue a compilation warning.
The string can be modified, but not appended to. Value parameters of type `String' (without a given capacity) assume as capacity the length of the actual parameter, in this case 3.
To avoid it, you can declare the parameter of a given capacity, say `String (40)' (via a type-declaration, of course). Then, of course, you have to specify in advance how big the parameter can become. (But otherwise the compiler would have to guess that, that's why it doesn't work like this.)
I would be interested to understand better how this works, because this behavior causes bugs in the programs I am porting from SUN Pascal and perhaps in ways I don't see as clearly as in this example.
I don't know what Sun Pascal does. In Borland Pascal, all strings are limited to 255 chars, so you'd get that effect with `String (255)'.
To avoid artificial limits, it's often useful to declare local variables with a capacity based on the actual parameter, e.g. in your case:
Procedure TstParams(S : String; IL : Integer); Var Tmp: String (Length (S) + 3); Begin Tmp := S; WriteLn('Before: S=', Tmp, ' IL=', IL); Tmp := Tmp + 'BBB'; { Using Concat() does the same } IL := IL + 4; WriteLn('After: S=', Tmp, ' IL=', IL); End;
Or perhaps (depending on what you need):
Var Tmp: String (2 * Length (S));
Frank
On 16 Sep 2005, at 10:32, Frank Heckenbach wrote:
Pascal Viandier wrote:
I just hit something I do not really understand in the way GPC passes parameters to procedures. The following sample program:
Program ParamTest; Var S40 : String(40); I : Integer;
Procedure TstParams(S : String; IL : Integer); Begin WriteLn('Before: S=', S, ' IL=', IL); S := S + 'BBB'; { Using Concat() does the same } IL := IL + 4; WriteLn('After: S=', S, ' IL=', IL); End;
Begin S40 := 'AAA'; I := 4; TstParams(S40, I); End.
Produces the following output: Before: S=AAA IL=4 After: S=AAA IL=8
How come the string cannot be modified inside of the procedure (without impacting the global string) but the integer can? Until now, my understanding of the parameters passing mechanism was that when you pass a parameter by value, the procedure uses a local copy of it, and this copy may be modified locally. This mechanism seems to work for integers but not for strings! However, if the string modification is somewhat "illegal" at least GPC could issue a compilation warning.
The string can be modified, but not appended to. Value parameters of type `String' (without a given capacity) assume as capacity the length of the actual parameter, in this case 3.
To avoid it, you can declare the parameter of a given capacity, say `String (40)' (via a type-declaration, of course). Then, of course, you have to specify in advance how big the parameter can become. (But otherwise the compiler would have to guess that, that's why it doesn't work like this.)
I would be interested to understand better how this works, because this behavior causes bugs in the programs I am porting from SUN Pascal and perhaps in ways I don't see as clearly as in this example.
I don't know what Sun Pascal does. In Borland Pascal, all strings are limited to 255 chars, so you'd get that effect with `String (255)'.
To avoid artificial limits, it's often useful to declare local variables with a capacity based on the actual parameter, e.g. in your case:
Procedure TstParams(S : String; IL : Integer); Var Tmp: String (Length (S) + 3); Begin Tmp := S; WriteLn('Before: S=', Tmp, ' IL=', IL); Tmp := Tmp + 'BBB'; { Using Concat() does the same } IL := IL + 4; WriteLn('After: S=', Tmp, ' IL=', IL); End;
Or perhaps (depending on what you need):
Var Tmp: String (2 * Length (S));
Frank
--
I too avoid length-unspecified strings.
But isn't Pascal nevertheless reporting a bug? It appears to be the equivalent of:
statement: Value of S: S:= 'AAA'; 'AAA' S:= S + 'BBB'; 'AAA'
I was able to duplicate this bug in gpc version 20041218, based on gcc-3.4.3. The problem is fixed by defining a type, an alternate approach to Frank's, if a fixed maximum string is permissible. - Willett Kempton
Program ParamTest; type s40type= string(40); Var S40 : s40type; I : Integer;
Procedure TstParams(S : s40type; IL : Integer); Begin WriteLn('Before: S=', S, ' IL=', IL); S := S + 'BBB'; { Using Concat() does the same } IL := IL + 4; WriteLn('After: S=', S, ' IL=', IL); End;
Begin S40 := 'AAA'; I := 4; TstParams(S40, I); End.
willett wrote:
I too avoid length-unspecified strings.
I didn't actually say I avoid them. The vast majority of my routines does not need to enlarge their string parameters (only modify the characters in place or possibly shorten it), so an unspecified capacity works fine there. And many of them actually don't modify their parameter at all in which case a `const' parameter (also of unspecified capacity) works best.
But isn't Pascal nevertheless reporting a bug? It appears to be the equivalent of:
statement: Value of S: S:= 'AAA'; 'AAA' S:= S + 'BBB'; 'AAA'
If S has a capacity of 3, that's so. EP might require a runtime check here (which GPC doesn't do yet), BP doesn't ...
I was able to duplicate this bug in gpc version 20041218, based on gcc-3.4.3. The problem is fixed by defining a type, an alternate approach to Frank's,
Actually, that's what I suggested first:
: To avoid it, you can declare the parameter of a given capacity, say : `String (40)' (via a type-declaration, of course).
That has the problem of the artifical limitation, that my second suggestion avoids.
Frank
Hi Franck,
I have to say, I understand your explanation but not the fundamental meaning of it. By inspecting the local variable with gdb, I see you are right. But since I declared the global string variable as a String(40), I expected the capacity passed to the procedure to be 40, not 3, which is the actual length, not the total string capacity . However, I think this is a rather strange behaviour. Why the original string capacity is not passed along with the value? In fact, this means the local string is not the same type as the one I declared globally and passed by value. Am I wrong?
The procedure where I discovered this receives strings of various lengths then it pads them with blanks up to the length of the integer parameter and displays them in reverse video. The original value must not be modified, and it must accept even literals values. So, if I declare a type with a fixed length (capacity), I will not be able to pass strings of various lengths to my procedure anymore... I will go for the local copy solution for now, but I think it would have been a good idea to pass the capacity of the string instead of its actual length...
Thanks for this fast answer
Pascal Viandier pascal@accovia.com
Pascal Viandier wrote:
I just hit something I do not really understand in the way GPC passes parameters to procedures. The following sample program:
Program ParamTest; Var S40 : String(40); I : Integer;
Procedure TstParams(S : String; IL : Integer); Begin WriteLn('Before: S=', S, ' IL=', IL); S := S + 'BBB'; { Using Concat() does the same } IL := IL + 4; WriteLn('After: S=', S, ' IL=', IL); End;
Begin S40 := 'AAA'; I := 4; TstParams(S40, I); End.
Produces the following output: Before: S=AAA IL=4 After: S=AAA IL=8
How come the string cannot be modified inside of the procedure (without impacting the global string) but the integer can? Until now, my understanding of the parameters passing mechanism was that when you pass a parameter by value, the procedure uses a local copy of
it,
and this copy may be modified locally. This mechanism seems to work for integers but not for strings! However, if the string modification is somewhat "illegal" at least GPC
could
issue a compilation warning.
The string can be modified, but not appended to. Value parameters of type `String' (without a given capacity) assume as capacity the length of the actual parameter, in this case 3.
To avoid it, you can declare the parameter of a given capacity, say `String (40)' (via a type-declaration, of course). Then, of course, you have to specify in advance how big the parameter can become. (But otherwise the compiler would have to guess that, that's why it doesn't work like this.)
I would be interested to understand better how this works, because this behavior causes bugs in the programs I am porting from SUN Pascal and perhaps in ways I don't see as clearly as in this example.
I don't know what Sun Pascal does. In Borland Pascal, all strings are limited to 255 chars, so you'd get that effect with `String (255)'.
To avoid artificial limits, it's often useful to declare local variables with a capacity based on the actual parameter, e.g. in your case:
Procedure TstParams(S : String; IL : Integer); Var Tmp: String (Length (S) + 3); Begin Tmp := S; WriteLn('Before: S=', Tmp, ' IL=', IL); Tmp := Tmp + 'BBB'; { Using Concat() does the same } IL := IL + 4; WriteLn('After: S=', Tmp, ' IL=', IL); End;
Or perhaps (depending on what you need):
Var Tmp: String (2 * Length (S));
Frank
-- Frank Heckenbach, frank@g-n-u.de, http://fjf.gnu.de/, 7977168E GPC To-Do list, latest features, fixed bugs: http://www.gnu-pascal.de/todo.html GPC download signing key: ACB3 79B2 7EB2 B7A7 EFDE D101 CD02 4C9D 0FE0 E5E8
Pascal Viandier wrote:
I have to say, I understand your explanation but not the fundamental meaning of it. By inspecting the local variable with gdb, I see you are right. But since I declared the global string variable as a String(40), I expected the capacity passed to the procedure to be 40, not 3, which is the actual length, not the total string capacity . However, I think this is a rather strange behaviour. Why the original string capacity is not passed along with the value? In fact, this means the local string is not the same type as the one I declared globally and passed by value. Am I wrong?
That's right -- and that's a general property of value parameters. E.g., if you declare an integer subrange variable and pass it to an integer parameter (or vice versa), or an integer variable to a real parameter, or a char to a string parameter, the actual parameter will not have the same type as the formal parameter.
Indeed, in this case it's more surprising, but actually, the alternative would be surprising in even more subtle ways. Then, e.g., these two calls could produce different results:
var a: String (10);
procedure p (s: String); begin ... end;
begin a := 'foo'; p (a); p ('foo'); end.
This would clearly violate the properties of value parameters.
The procedure where I discovered this receives strings of various lengths then it pads them with blanks up to the length of the integer parameter and displays them in reverse video. The original value must not be modified, and it must accept even literals values. So, if I declare a type with a fixed length (capacity), I will not be able to pass strings of various lengths to my procedure anymore... I will go for the local copy solution for now, but I think it would have been a good idea to pass the capacity of the string instead of its actual length...
In this case, I'd recommend the local copy. You can declare the local variable as big as necessary, based on the integer parameter.
This will cause an additional string copy. But OTOH, you can then declare the string parameter `const' (as it will not be modified). This will save one string copy again (possibly modulo a GPC bug which will be fixed soon), so in the end you don't really lose speed, if that's a concern.
Actually, it's even better than passing the capacity, as this will work then (which wouldn't when passing the capacity):
var a: String (5);
begin a := 'foo'; p (a, 10); end.
In your case, it could only pad to 5 chars. With the local variable it can pad to 10 chars as I suppose it should.
Frank
Frank Heckenbach a écrit:
Pascal Viandier wrote:
I have to say, I understand your explanation but not the fundamental meaning of it. By inspecting the local variable with gdb, I see you are right. But since I declared the global string variable as a String(40), I expected the capacity passed to the procedure to be 40, not 3, which is the actual length, not the total string capacity . However, I think this is a rather strange behaviour. Why the original string capacity is not passed along with the value? In fact, this means the local string is not the same type as the one I declared globally and passed by value. Am I wrong?
That's right -- and that's a general property of value parameters. E.g., if you declare an integer subrange variable and pass it to an integer parameter (or vice versa), or an integer variable to a real parameter, or a char to a string parameter, the actual parameter will not have the same type as the formal parameter.
Indeed, in this case it's more surprising, but actually, the alternative would be surprising in even more subtle ways. Then, e.g., these two calls could produce different results:
var a: String (10);
procedure p (s: String); begin ... end;
begin a := 'foo'; p (a); p ('foo'); end.
This would clearly violate the properties of value parameters.
You are right. Still I would consider as "more natural" the rule: with a value String parameter (without explicit capacity), - copy the original string with its capacity if it has one: p(a) - supply capacity = length if not : p('foo')
"more natural" means that I would be able to understand directly the surprising behaviour observed originally by Pascal, without having to ask to somebody who knows so well the internals of gpc.
Maurice
On 17 Sep 2005, at 04:31, Maurice Lombardi wrote:
Frank Heckenbach a écrit:
Pascal Viandier wrote:
I have to say, I understand your explanation but not the fundamental meaning of it. By inspecting the local variable with gdb, I see you are right. But since I declared the global string variable as a String(40), I expected the capacity passed to the procedure to be 40, not 3, which is the actual length, not the total string capacity . However, I think this is a rather strange behaviour. Why the original string capacity is not passed along with the value? In fact, this means the local string is not the same type as the one I declared globally and passed by value. Am I wrong?
That's right -- and that's a general property of value parameters. E.g., if you declare an integer subrange variable and pass it to an integer parameter (or vice versa), or an integer variable to a real parameter, or a char to a string parameter, the actual parameter will not have the same type as the formal parameter. Indeed, in this case it's more surprising, but actually, the alternative would be surprising in even more subtle ways. Then, e.g., these two calls could produce different results: var a: String (10); procedure p (s: String); begin ... end; begin a := 'foo'; p (a); p ('foo'); end. This would clearly violate the properties of value parameters.
You are right. Still I would consider as "more natural" the rule: with a value String parameter (without explicit capacity),
- copy the original string with its capacity if it has one: p(a)
- supply capacity = length if not : p('foo')
"more natural" means that I would be able to understand directly the surprising behaviour observed originally by Pascal, without having to ask to somebody who knows so well the internals of gpc.
Maurice
-- Maurice Lombardi
In such cases our intuitions may differ. This is why we have standards.
Tony Heatherington's brief description of the Extended Pascal standard follows. Tony was on the EP committee, and he has implemented a very high-quality EP compiler. (from: http://www.prosperosoftware.com/EPIntro.html#7)
----------- Heatherington -------------------------------- 7. Strings In classic Pascal, the only string facilities are associated with packed arrays of char. This is another area in which a variety of local extensions have arisen. Extended Pascal includes provision for dynamic string types, and unifies them with classic Pascal strings and with characters.
String variables are declared with a maximum capacity, for instance:
VAR s1,s2: string(20); fname: PACKED ARRAY [1..20] OF char; String values have a length (number of characters). A dynamic string variable such as s1 can hold a value of any length from zero up to its capacity, and the object code keeps track of the current length. With a fixed string such as fname, as found in classic Pascal, the length of the contents is equal to the capacity; when a shorter value is assigned to fname, it is padded on the right with spaces until it fits. A variable of type char has a capacity of 1. Variables of these three kinds, together with string literals and character constants, produce general string values. In addition, individual characters or substrings of string variables can be referenced by indexing, for instance s1[i] or fname[1..8].
String values can be concatenated using the + operator, and constants can be defined by constant expressions of string type, eg. 'ABC'+chr (13). There are predeclared functions for the commonly-required string operations such as locating a substring within a longer string.
Strings can be written to or read from textfiles, and versions of the textfile read and write procedures are provided which take a string variable in place of the file, making all the conversion and editing processes available internally.
A string may be declared with capacity fixed at compile time, as in the example above, or defined by a run-time variable expression. There are also provisions for formal parameters which adjust themselves to the actual parameter at each call.
PROCEDURE p (VAR s: string) Dynamic strings of different capacities may be passed to this procedure with each call; the code within the procedure can discover the capacity of each actual parameter by reference to s.capacity.
If a variable n has the value 10, a string declared as string(n) has the same type as one declared as string(10) for compatibility purposes (though the type checking cannot be performed until run- time). As will be seen in the next section, this rule and the adaptable formal parameters are both particular cases of facilities that apply to all schematic types, and arise from string being formally defined to be a predeclared "schema" with additional special properties.
--------------------------------------------
The Extended Pascal Standard itself, unfortunately, is very hard to read in this regard because to understand parameter passing of "string" you have to understand a lot of terminology that exists elsehere in the document. The section on value parameter passing is 6.7.3.2. I'll attach an image of the main part of that section (sorry my PDF is an image file; anyone who has a character-based PDF, please send me it!) It says that the formal parameter, when the procedure is activated, has the same TYPE as the actual parameter. I believe this would mean that, as Tony says far more briefly, for a variable as actual parameter, the formal parameter takes on the capacity rather than the length.
-----------------------
Upshot: Maurice's intuition appears to match the Extended Pascal Standard (at least for variables), and is the same as Tony Heatherington's reading of the Standard.
However, Pascal Vandever's original case was passing a constant string, in which case I believe (not totally sure) the length=capacity, so gpc works consistently with EP already i n that case.
- Willett Kempton
------------------ Extended Pascal standard (attached as image (sorry) ) ----------------------------
willett a écrit:
sorry my PDF is an image file; anyone who has a character-based PDF, please send me it
Using gsview Edit/ Text Extract on file iso10206_a4.ps found somewhere in the gpc web site gives acceptable results in this case. You obtain e.g.
6.7.3.2 Value parameters An actualparameter contained in the activationpoint of an activation of a block and corresponding to a formal value parameter of the block shall be an expression. Within the activation, the formalparameter and its associated variableidentifier shall denote the variable contained by the activation and corresponding to the variableidentifier (see 6.2.3.2). Within the commencement (see 6.2.3.8) of the activation, the value of the expression shall be attributed to the variable. The type possessed by the formalparameter shall be one permitted as the componenttype of a filetype (see 6.4.3.6). If the parameterform of the valueparameterspecification contains a schemaname that denotes the schema denoted by the required schemaidentifier string, then each corresponding actual parameter contained by the activationpoint of an activation shall possess a type having an underlyingtype that is a stringtype or the chartype; it shall be an error if the values of these underlyingtypes, associated with the values denoted by the actualparameters, do not all have the same length. Within the activation, each corresponding formalparameter shall possess the type produced from the schema string with the tuple having that length as its component. The initial state of the formalparameters shall be totallyundefined. The formalparameters and their associated variableidentifiers shall possess the bindability that is nonbindable. If the parameterform of the valueparameterspecification contains a schemaname that does not denote the schema denoted by the required schemaidentifier string, each corresponding actualparameter contained by the activationpoint of an activation shall possess a type having the underlyingtype produced from the schema denoted by the schemaname with a tuple, and it shall be a dynamicviolation if these tuples are not the same. Within the activation, the corresponding formalparameter shall possess the type produced from the schema with that tuple. The initial state of the formalparameters shall be the initial state associated with the tuple by the schema. The bindability of the formalparameters and their associated variable identifiers, and the bindability associated by the schema with each tuple in the schema's domain, shall be nonbindable. NOTE --- If the types derived from such a schema are subrangetypes or settypes then no actual parameter expression can satisfy these requirements since a primary of a subrangetype is treated as if it were of the hosttype and a primary of a settype is treated as if it were of the appropriate unpacked canonicalsetofTtype or packedcanonicalsetofTtype. If the parameterform of the valueparameterspecification contains a typename or a type inquiry, each formalparameter associated with an identifier in the identifierlist in that value parameterspecification shall possess the type denoted by the typename or typeinquiry, respectively. The value in the underlyingtype of the type of each corresponding actualparameter, associated with the value of the actualparameter (see 6.4.2.5), shall be assignmentcompatible with the type possessed by the formalparameters. The initial state of the formalparameters shall be the initial state denoted by the typename or typeinquiry. The bindability of the formalparameters and their associated variableidentifiers, and the bindability denoted by the typename or type inquiry, shall be nonbindable.
You have to do it once on thewhole file and save it
Maurice
willett wrote:
In such cases our intuitions may differ. This is why we have standards.
Tony Heatherington's brief description of the Extended Pascal standard follows. Tony was on the EP committee, and he has implemented a very high-quality EP compiler. (from: http://www.prosperosoftware.com/EPIntro.html#7)
----------- Heatherington -------------------------------- There are also provisions for formal parameters which adjust themselves to the actual parameter at each call.
PROCEDURE p (VAR s: string)
The Extended Pascal Standard itself, unfortunately, is very hard to read in this regard because to understand parameter passing of "string" you have to understand a lot of terminology that exists elsehere in the document.
Unfortunately so, as always.
The section on value parameter passing is 6.7.3.2. I'll attach an image of the main part of that section (sorry my PDF is an image file; anyone who has a character-based PDF, please send me it!)
I'm not sure if everyone here likes getting a 200KB attachment for a few lines of text. :-( There are PS versions of the standards linked on our homepage under "Resources". I think Chuck Falconer once provided a text version.
It says that the formal parameter, when the procedure is activated, has the same TYPE as the actual parameter.
I can't see this. It says:
: If the parameterÂform of the valueÂparameterÂspecification contains a : schemaÂname that denotes the schema denoted by the required : schemaÂidentifier string, then each corresponding actualÂparameter contained : by the activationÂpoint of an activation shall possess a type having an : underlyingÂtype that is a stringÂtype or the charÂtype; it shall be an error : if the values of these underlyingÂtypes, associated with the values denoted : by the actualÂparameters, do not all have the same length. Within the : activation, each corresponding formalÂparameter shall possess the type : produced from the schema string with the tuple having that length as its : component.
I.e., its type is a string with a component [i.e., capacity] which is the length of the actual parameter.
Upshot: Maurice's intuition appears to match the Extended Pascal Standard (at least for variables), and is the same as Tony Heatherington's reading of the Standard.
This quote applies to var (reference) parameters, not to value parameters. Whether the actual parameter is a constant of variable doesn't matter.
Frank
Maurice Lombardi wrote:
Indeed, in this case it's more surprising, but actually, the alternative would be surprising in even more subtle ways. Then, e.g., these two calls could produce different results:
var a: String (10);
procedure p (s: String); begin ... end;
begin a := 'foo'; p (a); p ('foo'); end.
This would clearly violate the properties of value parameters.
You are right. Still I would consider as "more natural" the rule: with a value String parameter (without explicit capacity),
- copy the original string with its capacity if it has one: p(a)
- supply capacity = length if not : p('foo')
1. How does this differ from Pascal's original assumption (which violates value parameter properties, as I explained, as you admit is right)?
2. How does this really solve Pascal's problem? In particular, consider this example from my previous mail:
: [T]his will work then [i.e., the way it's now], (which wouldn't : when passing the capacity): : : var a: String (5); : : begin : a := 'foo'; : p (a, 10); : end. : : In your case, it could only pad to 5 chars. With the local variable : it can pad to 10 chars as I suppose it should.
(And `p ('foo', 10)' could not pad at all -- just like now.)
"more natural" means that I would be able to understand directly the surprising behaviour observed originally by Pascal, without having to ask to somebody who knows so well the internals of gpc.
It's not really GPC internals. It's the behaviour specified in the EP standard.
Frank