Recent

Author Topic: Little bit confusing PChar operation  (Read 2959 times)

egsuh

  • Hero Member
  • *****
  • Posts: 1490
Little bit confusing PChar operation
« on: June 14, 2024, 07:03:14 am »
I have some difficulty with PChar operation. Whole structure is quite complex, but the gist is "PChar" in record.  The key is, in following example, AsPChar is as I defined in TaqVar.Evaluate, but not in outer scope. Please see the last comment.

Code: Pascal  [Select][+][-]
  1. type
  2.     TaqValue = record
  3.        case ValueType: TokenTypes of
  4.           IntValue : (AsInteger: integer);
  5.           ValList : (AsList: TaqList);
  6.           Bool : (AsBoolean : Boolean);
  7.           Range: (AsRange: TRange);
  8.           FloatValue : (AsFloat: Real);
  9.           StrVal, Error : (AsPChar: PChar); // string No. PChar.
  10.      end;
  11.  
  12.    TToken = class
  13.       property LChild : TToken read FLChild write setLChild;
  14.       property RChild : TToken read FRChild write setRChild;
  15.  
  16.       FRValue: TaqValue;
  17.       procedure Evaluate; virtual;
  18.      // and many other methods and properties
  19.    end;
  20.  
  21.    TaqVar = class(TToken)
  22.       procedure Evaluate; override;   // overrides, but do NOT call TToken.Evaluate
  23.       // nothing else in this class.
  24.    end;
  25.  
  26.    TExpression = class(TToken)
  27.       procedure Evaluate; override;
  28.    end;
  29.  
  30. implementation
  31.  
  32. procedure TaqVar.Evaluate;
  33. begin
  34.    if EvaluateVAR <> nil
  35.       then FRValue.AsPChar:= PChar(EvaluateVar(Self))  // EvaluateVar returns "32"
  36.       else FRValue.AsPChar:= PChar(IntToStr(cvUndefined));
  37.  
  38.     // Here ShowMessage(FRValue.AsPchar) display "32", for example
  39. end;
  40.  
  41. procedure TExpression.Evaluate;
  42. begin
  43.    if RChild <> nil then    { RChild is an TaqVar }
  44.      RChild.Evaluate;  // whithin this, AsPChar is "32", in the TaqVar.Evaluation
  45.  
  46.      // Here ShowMessage(RChild.FRValue.AsPchar) display "??  ??? " etc.
  47. end;

If I use shortstring instead of PChar, the problem disappears. How can I keep the content of PChar?

Thaddy

  • Hero Member
  • *****
  • Posts: 16184
  • Censorship about opinions does not belong here.
Re: Little bit confusing PChar operation
« Reply #1 on: June 14, 2024, 07:54:17 am »
pchars in a record need a known length. pchars are - obviously - pointers, so if you try to use them without allocated memory it goes BOOM.
Shortstrings are part of the record itself, but need to be declared with a fixed length. example;
Code: Pascal  [Select][+][-]
  1. type
  2.    TRecord1  = record
  3.       s:string[255]
  4.    end;
  5.    TRecord2  = record
  6.       s:PChar;
  7.    end;
  8. var
  9.   a:TRecord1;
  10.   b:TRecord2;
  11. begin
  12.   a.s:='this is a shortstring';
  13.   b.s:='this is a pchar';
  14.   writeln(SizeOf(a));
  15.   writeln(SizeOf(b));
  16. end.
In the first record, the content of the string is included in the record, in the second only the pointer is part of the record, not any content, iow an indirection.
The difference is very important. If you store the first record to disk you can read it back and the content will be the same. If you write the second to disk and read it back the content is lost.






i

« Last Edit: June 14, 2024, 08:03:37 am by Thaddy »
If I smell bad code it usually is bad code and that includes my own code.

Jorg3000

  • Jr. Member
  • **
  • Posts: 71
Re: Little bit confusing PChar operation
« Reply #2 on: June 14, 2024, 08:02:20 am »
Hi!
Why a PChar or a ShortString?
Why not String? (AnsiString)

Thaddy

  • Hero Member
  • *****
  • Posts: 16184
  • Censorship about opinions does not belong here.
Re: Little bit confusing PChar operation
« Reply #3 on: June 14, 2024, 08:05:56 am »
Ansistrings are also pointers - but with the length stored - so have the same problems as I demonstrated:
If part of a record, Stored and read back the content is lost!!!
If I smell bad code it usually is bad code and that includes my own code.

Khrys

  • Full Member
  • ***
  • Posts: 105
Re: Little bit confusing PChar operation
« Reply #4 on: June 14, 2024, 09:14:55 am »
Quote
Code: Pascal  [Select][+][-]
  1. procedure TaqVar.Evaluate;
  2. begin
  3.    if EvaluateVAR <> nil
  4.       then FRValue.AsPChar:= PChar(EvaluateVar(Self))  // EvaluateVar returns "32"
  5.       else FRValue.AsPChar:= PChar(IntToStr(cvUndefined));
  6.  
  7.     // Here ShowMessage(FRValue.AsPchar) display "32", for example
  8. end;

Both  IntToStr  and  EvaluateVar  return  AnsiString, meaning that this procedure goes like this:
  • Whichever branch is taken, assign the returned string to an invisible temporary with reference count 1
  • Store address (PChar) of that string's data in  FRValue
  • Procedure exits, decrementing the reference count of all local strings. The temporary's refcount reaches 0, meaning that it is deallocated as soon as the procedure is done
  • FRValue.AsPChar  now points to freed/garbage memory
If you want  PChar  to keep the data it points to, you'll have to give it ownership of its data - and manage it manually, just like with  char*  in C (they're the exact same). StrNew  and  StrDispose  would be fit for this, or you could take the path of least resistance and use  AnsiString  instead

Thaddy

  • Hero Member
  • *****
  • Posts: 16184
  • Censorship about opinions does not belong here.
Re: Little bit confusing PChar operation
« Reply #5 on: June 14, 2024, 09:33:35 am »
Working with records, it is usually better to use shortstrings or fixed length array of char/bytes if the max length is known.

That gets rid of any redirections.
Code: Pascal  [Select][+][-]
  1. type
  2.    TRecord3  = record
  3.       s:array[0..255] of char;
  4.    end;
If you declare such records inside a procedure or function it is best to call Default() so the record is initialized.

A somewhat more complete example to show the dfferences is:
Code: Pascal  [Select][+][-]
  1. {$mode objfpc}{$modeswitch advancedrecords}{$H+}
  2. uses classes;
  3. type
  4.    TRecord1  = record
  5.       s:string[255]
  6.    end;
  7.  
  8.    { See TRecord4 for a better option }
  9.    TRecord2  = record
  10.       s:PChar;
  11.    end;
  12.  
  13.    TRecord3  = record
  14.       s:array[0..255] of char;
  15.    end;
  16.   { be careful if you want to store such records }
  17.    TRecord4  = record
  18.       l:integer;
  19.       s:Pchar;
  20.       function read:string;
  21.       procedure write(const value:string);
  22.    end;
  23.    
  24.    function TRecord4.Read:string;
  25.    begin
  26.      setstring(Result,s,l);
  27.    end;
  28.  
  29.    procedure TRecord4.write(const value:string);
  30.    begin
  31.      l:=length(value);
  32.      s:=PChar(value);
  33.    end;
  34.    
  35. var
  36.   a:TRecord1;
  37.   b:TRecord2;
  38.   c:TRecord3;
  39.   d:TRecord4;
  40. begin
  41.   a.s:='this is a shortstring';
  42.   b.s:='this is a pchar';
  43.   c.s:='this is an array[] of char';
  44.  writeln(SizeOf(a));
  45.  writeln(SizeOf(b));
  46.  writeln(SizeOf(c));
  47.  d.write('this is a pchar');
  48.  writeln(d.read);
  49.  { or simply }
  50.  writeln(d.s);
  51. end.
« Last Edit: June 14, 2024, 10:53:26 am by Thaddy »
If I smell bad code it usually is bad code and that includes my own code.

jcmontherock

  • Sr. Member
  • ****
  • Posts: 271
Re: Little bit confusing PChar operation
« Reply #6 on: June 14, 2024, 09:50:36 am »
Why PChar still exist ? It's a pointer and we don't know where the value is located. Both takes a lot of bytes. Using Char, more especially in record, is more simpler and use less memory.
Windows 11 UTF8-64 - Lazarus 4RC1-64 - FPC 3.2.2

Thaddy

  • Hero Member
  • *****
  • Posts: 16184
  • Censorship about opinions does not belong here.
Re: Little bit confusing PChar operation
« Reply #7 on: June 14, 2024, 10:18:20 am »
That is only true if it is just a single char. If there are more you will need shortstring with a given length or an array of char with a fixed length. See my example above.
And no, it does not save bytes! You are even more confused than egsuh  ;)

(Plz note that you can omit the read function from TRecord4, but not the write procedure)
« Last Edit: June 14, 2024, 10:30:19 am by Thaddy »
If I smell bad code it usually is bad code and that includes my own code.

marcov

  • Administrator
  • Hero Member
  • *
  • Posts: 11942
  • FPC developer.
Re: Little bit confusing PChar operation
« Reply #8 on: June 14, 2024, 10:29:01 am »
Why PChar still exist ?

For external interfacing and in very lowlevel code. It is not meant as a string type for application level code.

(and if you do, read Khrys message. I could have written it myself )
« Last Edit: June 14, 2024, 10:31:03 am by marcov »

Thaddy

  • Hero Member
  • *****
  • Posts: 16184
  • Censorship about opinions does not belong here.
Re: Little bit confusing PChar operation
« Reply #9 on: June 14, 2024, 10:40:09 am »
Better question is why pointers still exist?
If I smell bad code it usually is bad code and that includes my own code.

egsuh

  • Hero Member
  • *****
  • Posts: 1490
Re: Little bit confusing PChar operation
« Reply #10 on: June 14, 2024, 11:11:37 am »
Thank you everyone. I thought as FRValue is external to the evaluate procedure the PChar content should be kept. I’ll search for other ways. I used to use shortstring and looking for ways to decrease memory consumption.

Thaddy

  • Hero Member
  • *****
  • Posts: 16184
  • Censorship about opinions does not belong here.
Re: Little bit confusing PChar operation
« Reply #11 on: June 14, 2024, 11:28:24 am »
Well, it is either/or.
Examples are the windows and linux api's where if a pchar is used there is always a length parameter too. As I wrote, if there is a known maximum length, just use an array of that length.
That will trade off memory use for speed. (can be a considerable speed advantage when you handle many records, since no copying takes place)
« Last Edit: June 14, 2024, 01:55:10 pm by Thaddy »
If I smell bad code it usually is bad code and that includes my own code.

egsuh

  • Hero Member
  • *****
  • Posts: 1490
Re: Little bit confusing PChar operation
« Reply #12 on: June 14, 2024, 02:59:53 pm »
Will this work?


Code: Pascal  [Select][+][-]
  1. type
  2.     TaqValue = record
  3.        AsString: string;
  4.  
  5.        case ValueType: TokenTypes of
  6.           IntValue : (AsInteger: integer);
  7.           ValList : (AsList: TaqList);
  8.           Bool : (AsBoolean : Boolean);
  9.           Range: (AsRange: TRange);
  10.           FloatValue : (AsFloat: Real);
  11.           // StrVal, Error : (AsPChar: PChar); // string No. PChar.
  12.      end;
  13.  
  14. var
  15.       FRValue: TaqValue;
  16. //............................
  17.  
  18. function GetAQValue (VType: TokenTypes; valstr:string) : TaqValue;
  19. begin
  20.       Result.ValueType := VType;
  21.       case VType of
  22.            IntValue: Result.AsInteger := StrToInt(valstr);
  23.            Bool: Result.AsBoolean:= StrToBool(valstr);
  24.            ValList: Result.AsBoolean := TaqList.Create(valstr);
  25.            Range: // .....
  26.            FloatValue: // ......
  27.            else AsString := valstr;
  28.     end;
  29. end;  

I had some beer, so I'll check this myself tomorrow morning.

Thaddy

  • Hero Member
  • *****
  • Posts: 16184
  • Censorship about opinions does not belong here.
Re: Little bit confusing PChar operation
« Reply #13 on: June 15, 2024, 06:33:18 am »
Code: Pascal  [Select][+][-]
  1. type
  2.     TaqValue = record
  3.        AsString: string;
  4.  
  5.        case ValueType: TokenTypes of
  6.           IntValue : (AsInteger: integer);
  7.           ValList : (AsList: TaqList); //<--- is a pointer type. Is it initialized?
  8.           Bool : (AsBoolean : Boolean);
  9.           Range: (AsRange: TRange);//<--- is probably a pointer type. Is it initialized?
  10.           FloatValue : (AsFloat: Real);
  11.           // StrVal, Error : (AsPChar: PChar); // string No. PChar. //<--- is a pointer type. Is it initialized?
  12.      end;
Where is the length/size for those pointer types? Look at my simple examples for TRecord2 and Trecord4. Also check the size.
 The weak points in your code are the pointer types. Also note strings in {$H+} mode are also pointer types. Except shortstrings or array[fixedlength] of char.
« Last Edit: June 15, 2024, 06:50:36 am by Thaddy »
If I smell bad code it usually is bad code and that includes my own code.

egsuh

  • Hero Member
  • *****
  • Posts: 1490
Re: Little bit confusing PChar operation
« Reply #14 on: June 15, 2024, 07:07:14 am »
First, I did some tests (not much) and confirmed that following approach works. I can mix fixed fields, methods, and variant fields in record.

Code: Pascal  [Select][+][-]
  1. type
  2.     RRec= record
  3.           ThingToDo: string;
  4.  
  5.           procedure SetRecValue(rtype:integer; AValue:string);
  6.  
  7.           case rectype: integer of
  8.                  1:  (Month: string);
  9.                  2:  (Date: integer);
  10.     end;
  11.  
  12. implementation
  13.  
  14. procedure RRec.SetRecValue(rtype:integer; AValue:string);
  15. begin
  16.        rectype := rtype;
  17.        case rectype of
  18.            1: Month := AValue;
  19.            2: Date := StrToInt(AValue);
  20.            else ThingToDo := Avalue;
  21.        end;
  22. end;
  23.  
           

Quote
Where is the length/size for those pointer types?

Both TaqList and TRange are classes, and their construction and destruction are managed somewhere else. This means I can use assignments like  A.FRValue = B.FRValue. Pointers are copied, and I had no problem in using this for a few years in many applications. 

 

TinyPortal © 2005-2018