Recent

Author Topic: PSA: Don't use FreeAndNil  (Read 17412 times)

alpine

  • Hero Member
  • *****
  • Posts: 1373
Re: PSA: Don't use FreeAndNil
« Reply #45 on: May 20, 2023, 10:23:55 pm »
*snip*
As swts , I'm also not sharing your vision for the exceptions, but that is another massive subject for discussion and clearly OOT.
It's a language feature that allows for certain things that are otherwise not possible. The idea to only use Exceptions for errors, is a bit like saying "I only use if statements on integer comparisons". Sure you can, but I don't see any reason why you would artifically limit your toolset in such a way. As I said with the STAX example, exceptions are the only way to build something like that.
Using the exceptions mechanism to form an alternative program flow is a poisonous practice, especially if exceptions are intentionally thrown to compromise the structured nature of the language. They (exceptions) are effectively longjmp-s to potentially unknown places, thus inherently difficult to trace and hard to comprehend.
I'm not going to investigate how clever is their use in STAX, the exceptions mechanism must be used for what is intended - for exceptions. 
"I'm sorry Dave, I'm afraid I can't do that."
—HAL 9000

440bx

  • Hero Member
  • *****
  • Posts: 5077
Re: PSA: Don't use FreeAndNil
« Reply #46 on: May 20, 2023, 10:27:41 pm »
the exceptions mechanism must be used for what is intended - for exceptions.
or more specifically, for _unpredictable_ situations.  Exceptions should not be used to handle predictable cases, e.g, division by zero.

(FPC v3.0.4 and Lazarus 1.8.2) or (FPC v3.2.2 and Lazarus v3.2) on Windows 7 SP1 64bit.

Warfley

  • Hero Member
  • *****
  • Posts: 1870
Re: PSA: Don't use FreeAndNil
« Reply #47 on: May 20, 2023, 10:44:08 pm »
Using the exceptions mechanism to form an alternative program flow is a poisonous practice, especially if exceptions are intentionally thrown to compromise the structured nature of the language. They (exceptions) are effectively longjmp-s to potentially unknown places, thus inherently difficult to trace and hard to comprehend.
You say it is a jump to unknown places, I mean the same is true for the use of function pointers or virtual methods. In fact, I argue that they are the same idea just the other way around. When you cann a virtual function or a function pointer, you are basically saying: "Here I have an action I can't perform myself, whoever is capable, do that". Basically it's a call up the chain to the entity that has been previously registered to be able to do a certain action.
An exception is the same just the other way around. You basically say: "Here I have some result I don't know how to handle, anyone who ever knows how to react to it, take over". So when a function pointer call goes up the chain to perform an action, an exception goes down the chain to pass an result. These are just two sides of the same coin.

Infact, writing your code to jump to as much unknown code as possible is today called "dependency injection" and is considered program good design (even though I personally would argue that like any language tool, such forms of polymorphism should be used when the situation warrants it, and not just brainlessly applied in every situation like it's a mantra to keep the bad spirits away)

And the important part is, it's an opt out mechanism, a caller can ignore the result value of a function, it can ignore global error variables like errno, but it cannot ignore an exception. If you have something that defenetly must be handled, you always want an exception, because if the caller wants to ignore it, it requires specific code to do this. A special action requires special code.

The same is btw true for virtual methods in OOP. If you want to not provide the capabilities to perform a certain action, you must explicetly overload the function with a noop. If you instantiate a class with an abstract function that was not overloaded, somewhere in the chain (which is analogous of not registering an exception handler), you will get a compiler warning and when you try to call the abstract function you get a crash.

Both of these things are quite similar, and they are valuable tools for delegating actions to an unknown point, determined at runtime, and both of them are explicetly opt out, which is really important for writing safe code, as you must always expect that you will make errors, and having to explicetly opt-out of safety mechanisms is much harder to do on accident than forgetting to opt-in.

I'm not going to investigate how clever is their use in STAX, the exceptions mechanism must be used for what is intended - for exceptions.
I'm not saying otherwise, in my STAX example your task being terminated is clearly an exception to what you would normally expect. Also when looking at the sockets API, an empty socket buffer is clearly an exception to the usual result of a read operation (which is data). I'm just saying that these are not errors. You can have exceptions that you expect or are prepared for.
« Last Edit: May 20, 2023, 11:02:11 pm by Warfley »

BeniBela

  • Hero Member
  • *****
  • Posts: 922
    • homepage
Re: PSA: Don't use FreeAndNil
« Reply #48 on: May 20, 2023, 11:08:09 pm »
The 16 unused bits of the 64-bit pointer  are perfect to store some tagging data.
I actually had a project once, where I had basically 2 fields, an enum of the type of data, and then a pointer to the data. As this was a very long running computation, that also required a lot of memory (i.e. a lot of these structures), I decided to just combine them in one pointer. This means that a lot of the loading and copying operations of such structures were just a single operation, while also saving like 4 whole bytes (1 for the enum and 3 for the padding). It's a desperate optimization but can be useful.

I saved 48 bytes

Because I wanted  to have reference counting to avoid dealing with free, I used an interface. But that is so bad . 8 bytes for the pointer to thedata, 8 bytes for enum and paddding. 32 bytes for the interface. The interface requires 8 byte for the interface VMT and 8 bytes for the class VMT. 8 bytes for reference counters. I do not know where the last 8 bytes come from. Another VMT?



One such symbolic execution engine (the one I personally have worked with) is KLEE, which runs on LLVM. As FPC has an LLVM backend, trying to get fpc generated code to run with KLEE is actually on my long list of projects I'll never have time to do (it's not that easy in practice, as KLEE provides a runtime for some C APIs, all the syscalls that fpc generates directly, need to first be mapped onto libc calls before it can be used with KLEE, which will catch and emulate the C calls).
But in theory you can just use a software like KLEE to explore all possible paths, and generate yourself a bunch of unitttests from those results. Also while exploring KLEE already tests for memory errors, so use-after-free would be discovered by KLEE directly, and we have found a few actual bugs in the GNU core utils with it, so it's actually practically useful on real software.


That would be really useful

Quote
...extend the output space through additional control paths ...

Now we have entered the realm of science fiction. If it takes you more than a few sentences to make a point you have no point at all! You're just fooling yourself. Let's go back to Object Pascal 101:
  • Absolutely NO exceptions in the constructor
  • Absolutely NO exceptions in the destructor
Can we agree? Thank you.

FPC can handle that

440bx

  • Hero Member
  • *****
  • Posts: 5077
Re: PSA: Don't use FreeAndNil
« Reply #49 on: May 20, 2023, 11:08:32 pm »
Basically it's a call up the chain to the entity that has been previously registered to be able to do a certain action.
An exception is the same just the other way around.
That chain is the most problematic part of the exception mechanism and a problem OOP shares by means of inheritance and overrides.

In "normal" (non-oop) programming, if there is a table of function pointers, more often than not, there is usually only one table for the entire program (not one per object/class) _and_ the pointers in that table are usually set only _once_ and remain invariant.  Additionally, a simple search in the text editor is usually all that is needed to find out where a particular pointer is being set.

Neither exceptions nor OOP offer that determinism, much less the simplicity to determine the final target.

(FPC v3.0.4 and Lazarus 1.8.2) or (FPC v3.2.2 and Lazarus v3.2) on Windows 7 SP1 64bit.

Warfley

  • Hero Member
  • *****
  • Posts: 1870
Re: PSA: Don't use FreeAndNil
« Reply #50 on: May 21, 2023, 12:44:39 am »
In "normal" (non-oop) programming, if there is a table of function pointers, more often than not, there is usually only one table for the entire program (not one per object/class) _and_ the pointers in that table are usually set only _once_ and remain invariant.  Additionally, a simple search in the text editor is usually all that is needed to find out where a particular pointer is being set.

Neither exceptions nor OOP offer that determinism, much less the simplicity to determine the final target.
Recently I started to, when I need polymorphism, mostly use function pointers passed as arguments to the function that needs polymorphism. E.g. I had a function that would scan the output of a program line by line (so it executes the program and reads the output lines during execution), and because the things I'm searching for are different for different programs that I can call this way, I provide the function that actually scans the output as parameter. It's much slimmer than any class definition, and I can always see where the function pointer comes from (I just need to look up from where the function is getting called, as these call chains are usually so flat at most 2 callers down the line). The important point is to keep it flat, and writing classes often invite to over stuff the classes with too many functionality and too many layers of inheritance, while functions are so limited that you necessarily keep it flat.

I also think that exceptions should be clearly annotated. For example in Haskell an exception is basically just part of the return type, so instead of returning an integer, your function returns either an exception or an integer. If you want to pass that exception down to the next function, you must incorporate it into your return value as well. Java does this pretty similarly, where each function has to be annotated which exceptions it can throw, and if your function does not annotate the exception you need to handle it:
Code: Java  [Select][+][-]
  1. void foo() throws MyException {  // Annotated that it can throw an exception of type MyException
  2.   throw MyException;
  3. }
  4.  
  5. void bar() { // No annotation -> no exceptions
  6.   foo(); // Compiler error, no try-catch but also no annotation to pass it upwards
  7. }

This way keeps the exception passing flat, as you are incentivised to deal with them as early as possible, unless you want every function to have an addition exception return value. It basically combines two ideas, first that the exception is basically just added to the output space of your function, and is just an alternative return value, similarly as many functions may return negative numbers for errors, but instead of relying on the output type to have unused space, it just adds the exception types to the output space. But also it still retains that you must handle them, and can't ignore them (unlike the function return value, that you don't need to read out).

It should be said that this system in Java failed because of inheritance and inconsistencies, basically there are exceptions that can always be thrown (null pointer error, out of memory, etc.) without annotation, and because exceptions inherit from oneanother, if you annotate the base exception, this also catches all the sub exceptions (e.g. if you want to catch an DecodeException but already annotated that you want to pass IO exceptions, the compiler won't notify you because a DecoderException inherits from IO exception). Which then usually degenerates to people just annotating the base exceptions everywhere making this whole system pointless.

But I think the main problem with exceptions is that there are two types, you either want them to completely crash your program (because it's an error), or you want to handle them, but if you handle them thats usually just 1-2 calls down the line. So some mechanism to incentivise flat exception handling would be nice. Right now all that can be done is to clearly document when exceptions can be thrown and where, and hope the programmer reads that and handles them accordingly

alpine

  • Hero Member
  • *****
  • Posts: 1373
Re: PSA: Don't use FreeAndNil
« Reply #51 on: May 21, 2023, 09:42:29 am »
As I said - OOT.

Using the exceptions mechanism to form an alternative program flow is a poisonous practice, especially if exceptions are intentionally thrown to compromise the structured nature of the language. They (exceptions) are effectively longjmp-s to potentially unknown places, thus inherently difficult to trace and hard to comprehend.
You say it is a jump to unknown places, I mean the same is true for the use of function pointers or virtual methods. In fact, I argue that they are the same idea just the other way around. When you cann a virtual function or a function pointer, you are basically saying: "Here I have an action I can't perform myself, whoever is capable, do that".
No, I'm not saying that. I'm saying "I can do that by myself, but somebody may prefer to do it differently".

Basically it's a call up the chain to the entity that has been previously registered to be able to do a certain action.
An exception is the same just the other way around. You basically say: "Here I have some result I don't know how to handle, anyone who ever knows how to react to it, take over". So when a function pointer call goes up the chain to perform an action, an exception goes down the chain to pass an result. These are just two sides of the same coin.
No. The function pointers and virtual functions are clearly declared. OTOH the exception can appear from virtually nowhere (or everywhere), skipping most of your code. Combined with the manual memory management of Object Pascal you are obliged to enclose all your code with try ... finally.

And the important part is, it's an opt out mechanism, a caller can ignore the result value of a function, it can ignore global error variables like errno, but it cannot ignore an exception. If you have something that defenetly must be handled, you always want an exception, because if the caller wants to ignore it, it requires specific code to do this. A special action requires special code.

The same is btw true for virtual methods in OOP. If you want to not provide the capabilities to perform a certain action, you must explicetly overload the function with a noop. If you instantiate a class with an abstract function that was not overloaded, somewhere in the chain (which is analogous of not registering an exception handler), you will get a compiler warning and when you try to call the abstract function you get a crash.
You shouldn't be able to instantiate such a class at the first place.

Both of these things are quite similar, and they are valuable tools for delegating actions to an unknown point, determined at runtime, and both of them are explicetly opt out, which is really important for writing safe code, as you must always expect that you will make errors, and having to explicetly opt-out of safety mechanisms is much harder to do on accident than forgetting to opt-in.
The difference is when you're using virtual functions, you have a clear "contract" (i.e. the method definition). When you throw an exception, you're just giving up. And it is not so bad until somebody starts to use it for another purpose e.g. to chop the whole call tree for example. And the worst thing is when somewhere in the middle the exception is just swallowed and didn't reach your code at all.

And still we're not talking about performance penalties, how correct is to transfer statuses through exceptions, multi threading complications, etc.
"I'm sorry Dave, I'm afraid I can't do that."
—HAL 9000

Warfley

  • Hero Member
  • *****
  • Posts: 1870
Re: PSA: Don't use FreeAndNil
« Reply #52 on: May 21, 2023, 10:36:53 am »
No, I'm not saying that. I'm saying "I can do that by myself, but somebody may prefer to do it differently".
Ähm... no? TStream cant handle a call to Read or Write, it literally needs a descendent class to do so.

No. The function pointers and virtual functions are clearly declared. OTOH the exception can appear from virtually nowhere (or everywhere), skipping most of your code. Combined with the manual memory management of Object Pascal you are obliged to enclose all your code with try ... finally.
They can only skip your code when you don't handle them. You as a programmer are in full control if your code get's skipped or not. If you don't want your code to be skipped, just use try-except. Exceptions should never skip code you as a programmer do not intend to skip. Also, you can skip code with virtual methods. When you have class with Virtual Method M, which you fill with code, the user can simply inherit from that class, override M and not call inherited in it, and all your precious code is skipped, without you being able to do anything against it (unlike exceptions, where if you don't want your code to be skipped, just catch it).

Also, as I have said in many posts before, you should always use try-finally even if no exceptions are present, because try-finally also triggers on all other forms of jumps like continue, exit or break. Try-Finally is not just for exceptions, it is a safeguard that if you want to prematurely exit that code (e.g. by calling exit), you don't have to think about all the cleanup.

You shouldn't be able to instantiate such a class at the first place.
And as I stated in my previous post, I think code that calls a function that can throw an exception, but does not handle that exception should also not compile (as it is the case in Haskell or Java). But we have to live with what we are dealt with, and FPC does for a matter of fact allow you to instantiate an abstract class, and also allows you to write code that ignores exceptions. Pascal is not a perfect language, who would have thought.

The difference is when you're using virtual functions, you have a clear "contract" (i.e. the method definition). When you throw an exception, you're just giving up. And it is not so bad until somebody starts to use it for another purpose e.g. to chop the whole call tree for example. And the worst thing is when somewhere in the middle the exception is just swallowed and didn't reach your code at all.
[/quote]
And with exceptions you have a "clear" contract through the exception definition. When an TCP connection class can throw an EConnectionClosedException, I as a programmer know exactly that to handle when a stream closes I just need to catch this specific exception. And all other exceptions I am not prepared to handle myself are thrown, they are not handled there. It's similar to a virtal/abstract class, where if you inherit from a class you can decide if you can handle certain virtual functions, or if you want to leave them as is and leaving it to the next class in the chain to implement them. And calling inherited is pretty equivalent to passing an exception along.

Again virtual methods and exceptions are the same idea, just in oposite directions. In one the chain is constructed bottom up, in the other one it's top down. Yet in both you delegate your execution to the first instance in the chain that registers a handler for it.

And still we're not talking about performance penalties, how correct is to transfer statuses through exceptions, multi threading complications, etc.
And this topic again..., first (especially windows SEH exceptions) are really fast, it's not much of a performance penalty, except that your CPU cache is invalidated. But aside from that, if you run into performance problems because of exceptions, we can talk, I have never seen that in reality. And if you do, and have millions of exceptions thrown each second, I agree, that are to many exceptions. But again, when is this ever a problem in a real application? I'm using exceptions quite a lot, and I have never run into performance problems because of them.

Also you are talking as if exceptions aren't a stable feature in all programming languages for the past 30 years. We know the problems and pitfalls quite well (e.g. you can't reraise exceptions across threads, simple as that)
« Last Edit: May 21, 2023, 10:48:15 am by Warfley »

alpine

  • Hero Member
  • *****
  • Posts: 1373
Re: PSA: Don't use FreeAndNil
« Reply #53 on: May 21, 2023, 12:06:04 pm »
No, I'm not saying that. I'm saying "I can do that by myself, but somebody may prefer to do it differently".
Ähm... no? TStream cant handle a call to Read or Write, it literally needs a descendent class to do so.
That is a "class abstraction" and doesn't contradict with my saying.

No. The function pointers and virtual functions are clearly declared. OTOH the exception can appear from virtually nowhere (or everywhere), skipping most of your code. Combined with the manual memory management of Object Pascal you are obliged to enclose all your code with try ... finally.
They can only skip your code when you don't handle them. You as a programmer are in full control if your code get's skipped or not. If you don't want your code to be skipped, just use try-except. Exceptions should never skip code you as a programmer do not intend to skip.
That's the meaning of word "obliged".

Also, you can skip code with virtual methods. When you have class with Virtual Method M, which you fill with code, the user can simply inherit from that class, override M and not call inherited in it, and all your precious code is skipped, without you being able to do anything against it (unlike exceptions, where if you don't want your code to be skipped, just catch it).
I'm talking about the caller's code, you're talking about the callee's code. Quite a difference.

Also, as I have said in many posts before, you should always use try-finally even if no exceptions are present, because try-finally also triggers on all other forms of jumps like continue, exit or break. Try-Finally is not just for exceptions, it is a safeguard that if you want to prematurely exit that code (e.g. by calling exit), you don't have to think about all the cleanup.
That is clearly out of (even deviated) topic. Sounds like a lecture.
 
You shouldn't be able to instantiate such a class at the first place.
And as I stated in my previous post, I think code that calls a function that can throw an exception, but does not handle that exception should also not compile (as it is the case in Haskell or Java). But we have to live with what we are dealt with, and FPC does for a matter of fact allow you to instantiate an abstract class, and also allows you to write code that ignores exceptions. Pascal is not a perfect language, who would have thought.
You shouldn't be able to instantiate an abstract class. As long for the uncatched exceptions, that is another subject. Let me remind you that my sole point was: The exceptions must be used only for what are they intended for - exceptions.

The difference is when you're using virtual functions, you have a clear "contract" (i.e. the method definition). When you throw an exception, you're just giving up. And it is not so bad until somebody starts to use it for another purpose e.g. to chop the whole call tree for example. And the worst thing is when somewhere in the middle the exception is just swallowed and didn't reach your code at all.
And with exceptions you have a "clear" contract through the exception definition. When an TCP connection class can throw an EConnectionClosedException, I as a programmer know exactly that to handle when a stream closes I just need to catch this specific exception. And all other exceptions I am not prepared to handle myself are thrown, they are not handled there. It's similar to a virtal/abstract class, where if you inherit from a class you can decide if you can handle certain virtual functions, or if you want to leave them as is and leaving it to the next class in the chain to implement them. And calling inherited is pretty equivalent to passing an exception along.
You are confusing two different cases. In polymorphism you know how to morph for the specific case. In handling exceptions, excluding the specific cases, you don't know what to do - just bailing-out hoping somebody knows how to deal it. But it is not guaranteed at all.

Again virtual methods and exceptions are the same idea, just in oposite directions. In one the chain is constructed bottom up, in the other one it's top down. Yet in both you delegate your execution to the first instance in the chain that registers a handler for it.
Your personal opinion.

And still we're not talking about performance penalties, how correct is to transfer statuses through exceptions, multi threading complications, etc.
And this topic again...
Yeah, it's already started.
"I'm sorry Dave, I'm afraid I can't do that."
—HAL 9000

Warfley

  • Hero Member
  • *****
  • Posts: 1870
Re: PSA: Don't use FreeAndNil
« Reply #54 on: May 21, 2023, 12:54:27 pm »
To cut it short I will just answer to this single point
Quote
You are confusing two different cases. In polymorphism you know how to morph for the specific case. In handling exceptions, excluding the specific cases, you don't know what to do - just bailing-out hoping somebody knows how to deal it. But it is not guaranteed at all.
How is that different from TStream read and write? When TStream implements ReadByte, it internally uses Read, which it does not implement itself. TStream does not know on how to get the data. It just jumps to an unknown location hoping that whoever instantiated the class provided an implementation for read.
In this case the caller is responsible for making sure to instantiate a class that provides the read code (e.g. a THandleStream), and the TStream implementation must just trust that someone will handle this call.

When you throw an exception it's the same, you jump to an unknown location and you hope that the caller makes sure to provide the correct functionality. It's both times up to the caller to make sure that the code will be correctly handled. For virtual functions by instantiating a class that provides the functionality, for exceptions by using try catch. Not catching the exception you need to handle is as much of a caller error as using TSream.Create instead of TMemoryStream.Create and expecting it to work. And if you forgett one of those, your program will crash

To give a great example on why exceptions and their property to jump to arbitrary code locations is so important, look no further than in the FCL the netdb unit, pascals very own dns resolver:
https://gitlab.com/freepascal.org/fpc/source/-/blob/main/packages/fcl-net/src/netdb.pp#L1642
This code does not include any error handling (probably because the author just forgot), when the sending fails, the code will just ignore the failure and to the end user of netdb there is no way to figure out if they don't get a result because there is no result, or if there was a network failure, or if the dns server host does not exist, etc. You just get no result.

The reason for this is that the basic Berkeley socket API does error handling with return values and global variables, an opt-in mechanism. If you forgett to check it, like is the case in netdb, then you will never notice that it has failed. Exceptions on the other hand are an opt-out mechanism. If you want to ignore this result, you must actively write code to do so, other wise you don't get a valid result and can't continue working.

Not having exceptions makes this code objectively more error prone than the exact same code but just with send and recv using exceptions.

(I know that this is an example of error handling, but the same holds for non error exceptions, e.g. If a TCP stream closes. Not an error but still something you want to force the user to catch before continuing with using the stream)
« Last Edit: May 21, 2023, 01:18:05 pm by Warfley »

PascalDragon

  • Hero Member
  • *****
  • Posts: 5904
  • Compiler Developer
Re: PSA: Don't use FreeAndNil
« Reply #55 on: May 21, 2023, 01:50:47 pm »
Per Design, the Value of nil must not 0 (Zero 0). Every other value which is not a valid pointer should useable. A better value is deadbeef or some of this nice acronyms. But it must not depend on the value (Zero 0). The name itself says enough nil = not in list.

But this are my (old) 2 cents. (BTW, i have enough popcorn beside me lol )
This is the problem with re-inventing nil, nil is already exactly what we want, so making the same again is going to be a dirty hack at all times. The only reason this example I posted should work (must admit haven't tested it as I'm currently not on a PC with FPC installed), is because modern 64 bit MMUs actually only support for 48 bit address ranges, so there never can be a valid memory address at this point.

While x64 CPU MMUs support only a specific range of bits the thing is that the available 48-bit address range is split into an upper and lower range: addresses with all leading bits 0 and addresses with all leading bits 1 with a gap of invalid addresses located between those two areas. Most OS will put the user space memory in the lower area and the kernel space memory in the upper one, but that is definitely not a requirement (e.g. in the OS we develop at my company the kernel resides in the lower area and the user space in the upper area). That means depending on the OS the address $ffffffff_ffffffff can be valid for a user space application.

I wonder what aarch64 does. I just spent the last weekend building a tagged pointer variant type.

Aarch64 has a similar memory hole that x86_64 has. Please note that on both platforms however you should check the necessary CPU registers to detetect what the real size of the hole is. While currently addresses are only 48-bit in length this is not set in stone and both architectures provide in theory the ability to extend this. So if you want your code to be future proof: don't abuse these unused bits.

Perhaps it would be desireable to have a compiler option to change the behaviour of Free() between Free and FreeAndNil so that both versions could be used while testing the programm without rewriting the code.

Does anyone want to comment on this?

TObject.Free is an ordinary method and we have no interest to change this, thus there can't be some kind of compiler switch to change this.

Again, can anybody offer a reason why it is necessary to set a variable to nil besides the example I offered earlier? Anything??? Anybody???

It's useful (and important) when you have circular references that are considered to own each other:

Code: Pascal  [Select][+][-]
  1. type
  2.   TClassB = class;
  3.  
  4.   TClassA = class
  5.     ClassB: TClassB;
  6.     destructor Destroy; override:
  7.   end;
  8.  
  9.   TClassB = class
  10.     ClassA: TClassA;
  11.     destructor Destroy; override;
  12.   end;
  13.  
  14. destructor TClassA.Destroy;
  15. begin
  16.   FreeAndNil(ClassB);
  17. end;
  18.  
  19. destructor TClassB.Destroy;
  20. begin
  21.   FreeAndNil(ClassA);
  22. end;
  23.  
  24. var
  25.   a: TClassA;
  26. begin
  27.   a := TClassA.Create;
  28.   try
  29.     a.ClassB := TClassB.Create;
  30.     a.ClassB.ClassA := a;
  31.   finally
  32.     a.Free;
  33.   end;
  34. end.

If the destructors wouldn't use FreeAndNil then the destructors would continue to call each other until there's a stack overflow. With FreeAndNil however the destructor is only called one the reference has already been set to Nil and thus the other destructor won't be called anymore.

This is obviously a very constructed example, but in more complex class hierarchies (especially if also event handlers are involved) such situations might easily happen. At my previous workplace there were situations where a FreeAndNil had to be used as otherwise there were bound to be crashes due to use-after-free while destructing.

Quote
...extend the output space through additional control paths ...
Now we have entered the realm of science fiction. If it takes you more than a few sentences to make a point you have no point at all! You're just fooling yourself. Let's go back to Object Pascal 101:
  • Absolutely NO exceptions in the constructor
  • Absolutely NO exceptions in the destructor
Can we agree? Thank you.

Exceptions inside constructors and destructors are just as valid as anywhere else in Object Pascal and both the compiler and the RTL handle them correctly (except maybe FreeAndNil due to it setting the reference to Nil first).

the exceptions mechanism must be used for what is intended - for exceptions.
or more specifically, for _unpredictable_ situations.  Exceptions should not be used to handle predictable cases, e.g, division by zero.

It depends on the use case. If I have a function that performs a division which might have 0 as divisor due to the provided parameters and that does not have an explicit error path (e.g. it doesn't return a Boolean to report success or failure, but instead the value it's supposed to calculate or some record it's supposed to fill) then having the division by zero exception trigger might just as well be fine. Of course one could also raise an EInvalidArgument instead during the parameter validation...

Swami With The Salami

  • Guest
Re: PSA: Don't use FreeAndNil
« Reply #56 on: May 21, 2023, 04:05:47 pm »
Quote
Exceptions inside constructors and destructors are just as valid as anywhere else in Object Pascal and both the compiler and the RTL handle them correctly (except maybe FreeAndNil due to it setting the reference to Nil first).

I think you mean "Exceptions inside constructors and destructors can occur as anywhere else..." - they are certainly not valid. Get a grip - we're not going to slice our birthday cake with a screw driver just because we can. The point is to make an effort to avoid exceptions. It's simply a matter of discipline and decorum.

jamie

  • Hero Member
  • *****
  • Posts: 6826
Re: PSA: Don't use FreeAndNil
« Reply #57 on: May 21, 2023, 04:19:39 pm »
I must admit, it's been some time since I've seen such long winded post from various users here, simply based on the
"FreeAndNil"

   I just can't imagine what this would look like if there was code/Function that could calculate a winning lotto ticket!
The only true wisdom is knowing you know nothing

kupferstecher

  • Hero Member
  • *****
  • Posts: 603
Re: PSA: Don't use FreeAndNil
« Reply #58 on: May 21, 2023, 05:45:50 pm »
TObject.Free is an ordinary method and we have no interest to change this, thus there can't be some kind of compiler switch to change this.

Actually i thought of making it part of the destructor (Destroy). But yes, the issue is the same there. Having a function result of the destructor wouldn't help, as it isn't used. So it would require some deeper hacks..

BrunoK

  • Hero Member
  • *****
  • Posts: 683
  • Retired programmer
Re: PSA: Don't use FreeAndNil
« Reply #59 on: May 21, 2023, 05:53:01 pm »
I must admit, it's been some time since I've seen such long winded post from various users here, simply based on the
"FreeAndNil"

   I just can't imagine what this would look like if there was code/Function that could calculate a winning lotto ticket!
Lucky we didn't get Grumpy onboard ;-)

 

TinyPortal © 2005-2018