POV-Ray : Newsgroups : povray.off-topic : Learning C# : Re: Learning C# Server Time
29 Jul 2024 04:27:59 EDT (-0400)
  Re: Learning C#  
From: Darren New
Date: 30 Sep 2012 14:09:35
Message: <50688adf$1@news.povray.org>
On 9/29/2012 23:13, Warp wrote:
> Why?

Separate compilation combined with actual enforcement of the rules. Sing# is 
designed to use the language, in part, to enforce security. You don't get 
stack-smash and buffer-overrun viruses because you can't smash the stack or 
overrun buffers, as an example.

> Since the compiler can track when the object goes out of scope,

But C++ can't do this, which is why you wind up with all kinds of smart 
pointer types. You can make it work, but you can also get it wrong.

If you take a mutex, and pass it to a subroutine by value, you've just 
screwed your lock, because you'll free the lock when you return from the 
subroutine, and you'll free the already-disposed lock a second time when you 
return from the call that passed it.

> it can just as easily automatically add the destructor call.

It also prevents you from calling the destructor twice.

For example, in one module, you have

void xyz(tracked one, tracked two) {
    destroy one;
}

In another, separately-compiled module, you have

void pdq() {
    tracked alpha = ...;
    tracked beta = ...;
    xyz(alpha, beta);
    alpha.use();
    beta.use(); // As written, this will crash.
    ...
}

How would you recommend the compiler deal with such a thing, if you don't 
have to annotate what's destructed and what isn't?

Singularity does it by making you declare the function like this:
void xyz([claimed] tracked one, tracked two) { destroy one; }
(or some such syntax)

Then, the compiler enforces at compile time that inside the body of xyz, you 
destroy item one exactly once, and that you don't destroy item two.

As another example:

void xyz(tracked one, tracked two) { destroy one; }

void abc(tracked three, tracked four) { xyz(three, four); }

void def() {
    tracked five = ...;
    tracked six = ...;
    abc(five, six);
    ...
}

Where does five go out of scope? Where does six go out of scope? Five and 
six are still in scope in def, but five is the same as three is the same as 
one, which is destroyed in xyz. So after the call to abc, five is no longer 
usable. Either that, or you have to say that xyz is incapable of actually 
discarding five, which means every tracked value has to be discarded in the 
same stack frame where it was created, which seems rather restrictive.

How does C++ do it? You create the file object at one stack frame, and then 
you only pass it by reference down thru the stack, right? So the thing 
really only gets destructed in the stack frame where you allocated it. Or 
you allocate it on the heap and hope that someone eventually cleans it up 
again, with smart pointers trying to automate that to some extent?



If "two" in "xyz" is declared as an "out" variable, you have to assign a new 
tracked item to it before returning, and the variable can't already have a 
value when you invoke xyz. If it's declared an "in out" variable (i.e., a 
pass-by-reference), then you can destroy it in the body, but only if you 
create a new one and assign it before you return. If it's an "in" variable 
and you destroy it, you have to put that in the type signature of the 
routine, so the caller knows it's gone.

The general technique is called "typestate", invented in the 80's as a 
general method by Robert Strom. :-)

(Sing# does some other cool things like that, too, such as letting you take 
pointers to the insides of structures on the stack, but tracking where you 
assign such pointers so they never outlast the stack frame they're pointing 
to. This lets you do things like handle network packet parsing with zero 
copying, while avoiding the possibility of leaking pointers or smashing the 
stack. It also tracks the state your communication channel state machine is 
in, making sure you don't try to send a message other than the one you 
declared you'd send at any given time, which eliminates a whole host of bugs 
you have to kludge around in languages like Hermes and Erlang where an 
unexpected packet type either gets ignored and accumulates forever in the 
buffer or causes the code to do something otherwise unexpected.)

Now, granted, in Singularity, the OS is garbage-collected also, so there are 
like 3 types that you actually have to destroy (shared buffers, pointers to 
shared buffers, and communication channels to pass those shared buffers 
over), so there's generally very little occasion to need to destroy 
something. You don't destroy threads, or locks, or memory allocations, or 
stuff like that. Just connections to other programs.

-- 
Darren New, San Diego CA, USA (PST)
   "They're the 1-800-#-GORILA of the telecom business."


Post a reply to this message

Copyright 2003-2023 Persistence of Vision Raytracer Pty. Ltd.