Jump to content

Dalija Prasnikar

Members
  • Content Count

    1061
  • Joined

  • Last visited

  • Days Won

    91

Everything posted by Dalija Prasnikar

  1. Dalija Prasnikar

    The Case of Delphi Const String Parameters

    It is called reference counting not bit flipping for a reason. There is no negative reference count. Any reference counted instance is destroyed when reference count reaches zero. Point of reference counting is that reference count is increased a points where instance (string, object, ...) gets additional reference - if you assign it to another variable, but also when you pass it as parameter. Most of the time passing parameters does not require increasing reference count, but compiler is not capable of analyzing whether this is required or not. This is why programmer must decide whether or not reference count trigger is required for some parameter or not, depending on the code within the procedure. If you want to skip unnecessary reference counting then you should mark parameter as const, if you need reference counting trigger then you don't. The only possible fix (the one Marco implied when he said that compiler will not change) would be that const parameter ALWAYS trigger reference counting. Since whole point of const when managed types are concerned is to prevent unnecessary reference counting and performance penalty, obviously that kind of fix would make no sense at all.
  2. Dalija Prasnikar

    The Case of Delphi Const String Parameters

    Programming languages share some common concepts. But it is important to distinguish things that are comparable and things that are not comparable. Definition of value and reference type does not change with language. There are various implementation details around both, but basic classification is the same. Value types are stored directly in memory associated by variable. And when you are accessing that memory you will always access valid memory as long as you can access that variable. Reference types have two parts, and variable only holds pointer to the actual data that is dynamically allocated on heap. That associated memory holding actual data, may or may not be allocated and your variable can point at invalid memory location that does not hold valid data. That crucial difference between value types and reference types also has huge impact on their behavior in any language. But the actual behavior will differ because every language has different memory management. So what happens and how memory of associated reference is handled cannot be easily compared - this is where you cannot say, Java does this... so Delphi should be able to do this too. And I said "Java has garbage collection, so you absolutely cannot compare it to what Delphi does." The fact that Delphi documentation does not specifically mentions value types and reference types that does not mean that this classification does not exist and that it does not have impact on behavior of different data types. Many things are not explicitly written in documentation, that does not mean they are not real. From http://docwiki.embarcadero.com/RADStudio/Sydney/en/Internal_Data_Formats_(Delphi) description of string type it is pretty clear that string variable holds pointer to actual data, and that is clear definition of reference type. You are confusing value of the string with value type. Value of the string is sequence of characters, but they are not directly stored in string variable, but in some other location. Value stored in the string variable itself is just pointer to that location.
  3. Dalija Prasnikar

    The Case of Delphi Const String Parameters

    Sure... yep... this is my imagination running wild... https://en.wikipedia.org/wiki/Value_type_and_reference_type There is no common sense there is clear definition what reference type is and what value type is. You can always see address stored in any reference type, including string. It just requires typecast.
  4. Dalija Prasnikar

    The Case of Delphi Const String Parameters

    Emphasis is on BEHAVE LIKE. It is not string IS value type. And simulating something is not equal to having that exact thing. string in Delphi is reference type. It is not value type. This is not a viewpoint it is a fact. If you cannot acknowledge facts, this discussion is pointless. s1 and Value are two different references to ONE memory location. When data in that memory location is destroyed because you change s1 variable that was only strong reference to that memory location, Value will point to invalid memory. I am talking about leaking abstractions (as implementation details cannot be completely hidden), not bugs. You cannot completely separate idea (concept) of a string and implementation of a string.
  5. Dalija Prasnikar

    The Case of Delphi Const String Parameters

    Copy on write is not equal to being value type. You may not like "implementation details" and "tricks" but they are more than implementation details, they achieve desired behaviors. You cannot have full value type behavior with something that is not value type. You can only simulate some parts, but that also means that implementation details of actually being a reference type and having ARC to handle its lifetime and COW leak through.
  6. Dalija Prasnikar

    The Case of Delphi Const String Parameters

    Compilers (languages) are always leaky abstractions. At some level you have to define what specific concept means and then that leads to its implementation. There is no universal concept of const parameters. There are many ways how parameters are passed and different languages greatly differ. String type is reference type, not a value type. Only short strings are value types. There is no conceptual here, at least not when Delphi is concerned. Your conceptual discussion would be discussing some completely different language.
  7. Dalija Prasnikar

    The Case of Delphi Const String Parameters

    Your posts clearly show that you have no idea how reference counting actually works, nor how compiler works. I don't mean this as insult, but merely as observation. Not knowing something is not a problem on its own, but it is hard to have meaningful discussion when other party does not have basic idea about discussed topics. I could try to explain it in more details, but I don't know from where should I start. Especially, because you seem to have wrong perception about how compiler works. I am worried that we would be running in circles. Also both topics have enough content around that covering everything from the start would require writing a book.
  8. Dalija Prasnikar

    The Case of Delphi Const String Parameters

    In theory concepts are one thing, compiler specifications and implementation details another. Unfortunately Delphi does not have formal compiler specification, and compiles implementation is often the only specification. Of course, there are always corner cases where it is hard to say whether some behavior is intended or it is a bug, but in this case - const parameters behaviors are pretty well defined. I don't think you fully grasp meaning of the above sentence. Yes, const means that you are not allowed to change parameter inside function and it does not necessarily imply passing by reference. But, you are forgetting that reference types consist of two parts - reference (that is the only part passed as parameter and cannot be changed) and associated content (data) that is allocated on a heap. Besides reference types, Delphi also has value types. And the optimization part of passing by reference is primarily for passing value types larger than register size. Since references (pointers) are not larger than register size, they will not be passed by reference, but by value. Code is not correct and it will fail, not because Value is somehow changed - it is not and it is fully compliant with definition of const parameters. Problem arises because being passed as const - where reference cannot be changed triggers reference counting optimization for reference counted types. Since parameter will not be changed, there is not need to take care of reference counting. Because reference counting is not triggered, original string (same would apply for any other reference counted type) where Value reference points to, will be destroyed when s1 is assigned new value - in that moment Value reference that was not changed will point to now invalid memory location. It is also important to note that actual reference count is maintained as part of heap allocated data - not inside reference variable.
  9. Dalija Prasnikar

    The Case of Delphi Const String Parameters

    I probably wasn't specific enough when I said there are no flaws in compiler. I meant only related to what @Kas Ob. was saying and his example does not have const parameters. Also, only object instances suffer from this particular bug.
  10. Dalija Prasnikar

    The Case of Delphi Const String Parameters

    You are mixing a lot of things here. First, Java has garbage collection, so you absolutely cannot compare it to what Delphi does. WriteItDown procedure is compiled independently of the call site - it has to cover all possible variants. So while your example only passes string literal that has special handling, WriteItDown still has to take care of situation where dynamically allocated string variable will be passed to it. Hence try...finally block. As for the rest is concerned I am not sure I am following what you want to say... what const are you talking about? WriteItDown does not have const parameter, so it increases and decreases reference count as expected. Again there is no flaw in compiler, is works exactly as intended. If you pass reference counted parameter as value it will trigger reference counting mechanism, if you pass it as const it will not. You can choose to make parameter const or not depending on what method does with it. And again special handling for string literals is required because compiler does not know what kind of string you passed in when you called the method.
  11. There are two problems with using Boolean parameters. First, is when like in Nick's example you are literally calling two separate functions to do something based on boolean parameter. If there is common logic shared inside the method, where making two distinct methods would violate DRY principle, then it is fine to have parameter that will make your method do two things. Another problem using Boolean parameters, when you have valid reason to make method do two things is that boolean on its own is very rarely descriptive. EnableDisableControls(True); Does the above call enable or disable controls? If you need to make method do two things, it is better to use enum with two values that will give you more descriptive code on the call site. In your example if the method would be called EnableControls, then using Boolean parameter would be self descriptive. It is pretty obvious what following line does: EnableControls(True); On the other hand boolean in following method makes code unreadable ListFiles(const APath: string; IncludeSubFolders: Boolean) Can you tell what following code does, unless you know ListFiles declaration by heart? ListFiles(Path, True); In such case enum, is much better option even though it would have only two states. ListFiles(Path, optIncludeSubFolders);
  12. Dalija Prasnikar

    The Case of Delphi Const String Parameters

    This is nature of reference types. It is possible to modify their content. Only reference is passed as constant, not the actual content. Being confused about reference types and how they are passed to the procedures is not Delphi specific thing. For instance, Java passes all parameters as values, but in case of reference types (objects) that does not mean content of the object cannot be changed, unless it is made immutable through some other means (for instance, not having setter or similar methods that would mutate content). That causes two misconceptions, first. that objects references in Java are passed by reference, not by value and second that otherwise mutable objects cannot be changed inside method. Of course, comparing to Java, Delphi has many more parameter modifiers and that certainly complicates situation and possible variations.
  13. Dalija Prasnikar

    The Case of Delphi Const String Parameters

    I wrote more elaborate post on const parameters https://dalijap.blogspot.com/2021/01/are-const-parameters-dangerous.html
  14. Dalija Prasnikar

    The Case of Delphi Const String Parameters

    This problem is pure ARC problem. One instance of something is destroyed when other one is created and assigned because const parameter did not trigger reference counting mechanism - this is why const is used as speed optimization in the first place. Same thing that happens with strings happens with reference counted objects, but it is easier to see what happens with objects. You need form with memo and button to test following code. IFoo = interface function GetCount: Integer; function GetNumber: Integer; end; TFoo = class(TInterfacedObject, IFoo) private FNumber: Integer; public constructor Create(ANumber: Integer); destructor Destroy; override; function GetCount: Integer; function GetNumber: Integer; end; constructor TFoo.Create(ANumber: Integer); begin inherited Create; FNumber := ANumber; Form1.Memo1.Lines.Add('Created ' + FNumber.ToString); end; destructor TFoo.Destroy; begin Form1.Memo1.Lines.Add('Destroyed ' + FNumber.ToString); inherited; end; function TFoo.GetCount: Integer; begin Result := FRefCount; end; function TFoo.GetNumber: Integer; begin Result := FNumber; end; procedure TForm1.Button1Click(Sender: TObject); var s1: IFoo; procedure Test(const Value: IFoo); begin s1 := TFoo.Create(456); Memo1.Lines.Add('Test Value ' + Value.GetNumber.ToString); end; begin s1 := TFoo.Create(123); Test(s1); end; Output is: Created 123 Created 456 Destroyed 123 Test Value 123 Destroyed 456 It is pretty obvious that object instance holding 123 is destroyed before its value is printed. Value.GetNumber.ToString is accessing dangling pointer - if the memory where object or string previously occupied is not reused, you may get expected value even though that instance is dead, but if memory is reused in the meantime you will read content of some other data. Also the whole thing might crash, or it might not. Basically you get undefined behavior.
  15. Thanks! About the delay... the final release was originally planned for December 11, but I had a minor accident on November 30, and ended up in ER, where I also tested positive for COVID! In less than a week whole family got infected. We all had mild symptoms and were left at home with no treatment., but living was pretty hectic for most of the following two weeks... Anyway, we managed to release it about 16 days later than expected (I already had a lot of it written before, just not fully ready for release) I would say that all this is behind us, as it is... but as you may have heard it... Croatia got hit by major earthquake today. This one was 50km from Zagreb where we're located, but it was 30 times stronger than the one that hit Zagreb in March. After 6.2 by Richter scale, local Nuclear Power Plant (Krsko) was shut down, and power is all but stable, which is why I may not be online as regularly as before in the coming days!
  16. It looks like the download link was not reset properly for the original version buyers. It should work now! If you're still having problems, please contact us through the website form at dalija.prasnikar.info. We may need additional information.
  17. Dalija Prasnikar

    Weak reference is dangerous

    Well, there is really nothing to document because nothing is really thread safe in Delphi. Unless you want every single documentation page having "This is not thread safe" (I am exaggerating a bit, but this is generally true in 99.99%)
  18. Dalija Prasnikar

    Weak reference is dangerous

    You use weak when you cannot guarantee that weak reference lifespan will be less or equal to original strong reference. For instance, let's pretend that TComponent is truly reference counted class, and that same goes for its descendants TEdit and TMenu. Edit has PopupMenu property that can be set to independent TMenu instance. Currently destroying such TMenu instance would leave dangling pointer in Edit PopupMenu property, but we have notification mechanism that notifies Edit that Menu will be destroyed and that Edit should clear reference to the menu. In imagined reference counting scenario, you could have popup menu marked as weak reference and you would not need any notification mechanism to clear that reference when PopupMenu instance is deleted because weak would zero out (clear) that reference when menu is destroyed. If you use unsafe instead of weak, then you would have dangling pointer in PopouMenu property.
  19. Dalija Prasnikar

    Weak reference is dangerous

    No. While unsafe can also be used to break reference cycles, it cannot be used interchangeably with weak, meaning unsafe used in wrong place can lead to dangling pointers. Again, using weak is perfectly fine. If you read my book example again, you will see that you cannot even safely take strong reference from strong reference if original has been written to. So now what? Don't use interfaces at all because they are not thread safe. Don't use strings and dynamic arrays because they are not thread safe?
  20. Dalija Prasnikar

    Weak reference is dangerous

    You are mixing apples and oranges. Weak references work fine and have their purpose when used in single thread scenario. Their purpose is not achieving some magical thread safety, but breaking reference cycles. I will say it again, assignment of any non trivial type in Delphi is not thread safe. Never has been. As soon as one thread is writing some data, all access to that data must be protected. There is no way around it.
  21. Dalija Prasnikar

    Weak reference is dangerous

    Assignment operation in Delphi are not thread safe. If you have one thread writing and other threads reading, such access must be protected or you will get crashes or buggy behavior. You can safely write and read 32bit aligned simple types, in terms that such code will not cause any crashing, but such code will also not work as intended, unless you don't care about actual values stored. So, weak references in Delphi are not thread safe. Also even strong interface references in Delphi are not thread safe. So there is no bug here, just bad test case. Excerpt from my book Delphi Event-based and Asynchronous Programming (Part 5. Thread Safety) If you have multiple threads accessing - reading and writing - the same reference, one thread can easily interfere with the other. Let's say we have a shared interface reference `Data`, pointing to a previously created object, one thread that sets that reference to nil, and another thread that tries to take another strong reference from that one with an assignment to another variable `Tmp`. The first thread will execute the `_IntfClear` function that will result in object destruction. The second thread, trying to grab a strong reference preventing object destruction, will execute the `_IntfCopy` function: var Data, Tmp: IInterface; Data := nil; // Thread 1 -> _IntfClear Tmp := Data; // Thread 2 -> _IntfCopy If the `Source._AddRef` line from the `_IntfCopy` function executes before the call to `IInterface(P)._Release` manages to decrease the reference count to zero and subsequently calls the object's destructor, all is good. The second thread will successfully capture another strong reference to the object instance. However, the first thread can interrupt the second thread at the wrong moment, just after the `Source <> nil` check was successfully passed, but before `Source._AddRef` had the chance to increment the object's reference count. In that case, the first thread will happily destroy the object instance while the second thread will happily grab a strong reference to an already nuked object, and you can forget about happily ever after. Note: Besides interface references to objects, the reference counting mechanism is also used for other types like strings and dynamic arrays. Assignment of those types is also not thread-safe.
  22. Dalija Prasnikar

    WinUI in Delphi (planned for 10.5?)

    64bit IDE is not question of easiness. It is also question of 3rd party libraries and design packages support. You cannot mix 32bit and 64bit DLLs so having 64bit IDE would mean everyone would need to provide their components and plugins as 64bit versions, too. Also, because you cannot mix 32bit and 64bit DLLs in a process, there is question of other parts of the Delphi IDE - this is not just Editor, but also compiler, linker, debugger... not everything is written in Delphi and porting any of the necessary parts to 64bit may not be straight forward. Easy part of 64bit transition is about Delphi applications, even though depending on your code you may have smooth or bumpy ride, too. I didn't have to do any adjustments for my code, because I never used any hacks around integer - pointers casting and I always had bitness explicitly specified for any data types I have used. So, generally most of transition to 64bit should be easy, but it also depends on specific projects and other libraries, plugins and interactions they might have and use. Scaling is bit different story... VCL Styles originally didn't have High DPI support, and when they were added to IDE theming in Rio they were still without High DPI support... now they have High DPI support, but tweaking the IDE to fully work under High DPI does require some work. This part would be the same with or without VCL Styles. I would first make IDE high DPI aware and then add styles... but that is me... why are some things done the way they are, I have no idea.
  23. Dalija Prasnikar

    Outdated Delphi Roadmap

    Yes. It can have huge impact. If some feature is scheduled for improvement, that means it will be looked at, but the more work feature needs, the more chances is that some parts will not be done right or some accidental bugs might creep in. Also public API can change only at major releases. If some API gets designed in a wrong way, it must stay like that until the next major release. I personally don't use floating point much, so I don't know how broad are required changes to improve performance and whether this will be some compiler tweaking or changes involved require significant API changes, too. In any way, it is always hard to predict different use cases and most important pain points, so having feedback from people that are experts in the area and that know what they need is crucial. Subscription is needed for regular users, but there are also exceptions for cases where people are invited because they can provide valuable feedback in certain areas.
  24. Runtime packages are not always ideal solution, so you have to weight your options. Just for fun... my installer application written in D7 is 1.1MB, same code recompiled in XE4 is 3.5MB, 10.4 4.7 MB. Windows 32bit Of course, no DLLs here... but even if you can argue that there are substantial differences and needed improvements between D7 and XE4, between XE4 and 10.4 when it comes to VCL new features were sparse, and certainly not large enough to justify size difference. I mean the whole darn setup in D7 with several forms fits into that difference (XE4-10.4)
  25. If you have many DLLs with bloat is duplicated. Also code size is not something that should be observed from disk size perspective. How do you deliver that code also matters. If you need to make frequent updates to some remote place with poor or expensive Internet connection then every byte counts. Literally.
×