Rocksolid Light

Welcome to novaBBS (click a section below)

mail  files  register  newsreader  groups  login

Message-ID:  

An Ada exception is when a routine gets in trouble and says 'Beam me up, Scotty'.


devel / comp.lang.c / destructor for C? (static analisys)

SubjectAuthor
* destructor for C? (static analisys)Thiago Adams
+* Re: destructor for C? (static analisys)Öö Tiib
|`* Re: destructor for C? (static analisys)BGB
| `* Re: destructor for C? (static analisys)Öö Tiib
|  `* Re: destructor for C? (static analisys)BGB
|   `- Re: destructor for C? (static analisys)Öö Tiib
+* Re: destructor for C? (static analisys)Richard Damon
|`- Re: destructor for C? (static analisys)Thiago Adams
`- Re: destructor for C? (static analisys)David Brown

1
destructor for C? (static analisys)

<af626e8a-ec10-45df-9e58-fd2a53f9cd9fn@googlegroups.com>

  copy mid

https://www.novabbs.com/devel/article-flat.php?id=24614&group=comp.lang.c++#24614

  copy link   Newsgroups: comp.lang.c
 by: Thiago Adams - Wed, 1 Feb 2023 20:05 UTC

I am working in a static analysis prototype that has the
mission to detect and emit an warming of missing free/destroy
operation.

(The idea is general but I am starting with free to explain)

The mechanism is like this:

We add attribute [[free]] at return type to tell the compiler
that someone must call some function that "free" the result before
the end of scope.

[[free]] malloc(size_t s)

Then we use the same attribute in some function argument

free([[free]] void * p);

int main() {
void * p = malloc(10);
free(p);
}

(The name of the function "free" is just a coincidence. It does not need
to be the same name of the attribute)

The general idea is that declarators (in this case p) have
"compile time imaginary flags".

So at this line

void * p = malloc(10);

the compile time imaginary flag "must free" is set on p.

calling free(p) the imaginary flag "must free" is cleared.

At the end of scope, by design the static analysis will
check if the "must free" tag was cleared. It must ensure
that it has a path that is always called.

If the ownership is transferred we can clear the flag manually.
For instance:

void * p = malloc(10);
void * p2 = p;
_clear_imaginary_flag(p, "must free");
free(p2);

We also can check at compile time these flags:

void * p = malloc(10);
static_assert(_has_imaginary_flag(p, "must free");
To avoid too much annotations some default behavior is useful.

For instance, returning a variable with "must free" flag will automatically
move the ownership.

[[free]] void * F() {
void * p = malloc(10);
return p;
}

There is much more details but I would like to present
the general idea and motivation.

So the motivation is to have the same guarantees of C++ destructor.
In other words, we have 100% certainty (at compile time) that some
destructor is called before the end of scope.

Next..level

In C APIs we have some conditional destructors. For instance,
we need to call fclose only if we have a non null FILE.

So differently than C++ we have a conditional destructor.
(In C++ the destructor is called unconditionally.)

A workaround is to have a and safe_fclose that also works with
null. This is how C++ works, they add a if on the destructor.

But a more interesting solution would have a better flow
analysis and check the conditional creation. But in this case we
also need to tell the static analysis about this situation.

[[conditional free]] FILE* fopen();
void fclose([[conditional free]] FILE* p);

We we think about the motivation we can see that
other static analysis are required like ownership transfer.

In general terms what I want is a "automatic review" with similar
capabilities of human.
I also want to give the final word to the programmer that at any moment can override the imaginary flag.

The main feedback I would like to have is :

What do you think about the idea of having a 100% compile time
guarantee that some function is called in C? (Similar of C++ destructor
guarantee but in this case we need to call it manually)

Re: destructor for C? (static analisys)

<be2386a7-209c-41ea-99a1-3caeff1427a8n@googlegroups.com>

  copy mid

https://www.novabbs.com/devel/article-flat.php?id=24615&group=comp.lang.c++#24615

  copy link   Newsgroups: comp.lang.c
 by: Öö Tiib - Wed, 1 Feb 2023 22:51 UTC

On Wednesday, 1 February 2023 at 22:05:17 UTC+2, Thiago Adams wrote:
>
> The main feedback I would like to have is :
>
> What do you think about the idea of having a 100% compile time
> guarantee that some function is called in C? (Similar of C++ destructor
> guarantee but in this case we need to call it manually)

What I would like is that tools as Valgrind are made more accessible to
novices and that everybody are taught to use those tools. That may be
hard but at least feels worth of any effort, however little.

Otherwise neither C nor C++ (nor garbage collection like in C#) does
guarantee that life-time of objects in program is well managed and
adding more syntax sugar might only confuse that fact.

Re: destructor for C? (static analisys)

<sMDCL.169983$5CY7.105475@fx46.iad>

  copy mid

https://www.novabbs.com/devel/article-flat.php?id=24616&group=comp.lang.c++#24616

  copy link   Newsgroups: comp.lang.c
 by: Richard Damon - Thu, 2 Feb 2023 00:49 UTC

On 2/1/23 3:05 PM, Thiago Adams wrote:
> I am working in a static analysis prototype that has the
> mission to detect and emit an warming of missing free/destroy
> operation.
>
> (The idea is general but I am starting with free to explain)
>
> The mechanism is like this:
>
> We add attribute [[free]] at return type to tell the compiler
> that someone must call some function that "free" the result before
> the end of scope.
>
> [[free]] malloc(size_t s)
>
> Then we use the same attribute in some function argument
>
> free([[free]] void * p);
>
> int main() {
> void * p = malloc(10);
> free(p);
> }
>
> (The name of the function "free" is just a coincidence. It does not need
> to be the same name of the attribute)
>
>
> The general idea is that declarators (in this case p) have
> "compile time imaginary flags".
>
> So at this line
>
> void * p = malloc(10);
>
> the compile time imaginary flag "must free" is set on p.
>
> calling free(p) the imaginary flag "must free" is cleared.
>
> At the end of scope, by design the static analysis will
> check if the "must free" tag was cleared. It must ensure
> that it has a path that is always called.
>
> If the ownership is transferred we can clear the flag manually.
> For instance:
>
> void * p = malloc(10);
> void * p2 = p;
> _clear_imaginary_flag(p, "must free");
> free(p2);
>
> We also can check at compile time these flags:
>
> void * p = malloc(10);
> static_assert(_has_imaginary_flag(p, "must free");
>
> To avoid too much annotations some default behavior is useful.
>
> For instance, returning a variable with "must free" flag will automatically
> move the ownership.
>
> [[free]] void * F() {
> void * p = malloc(10);
> return p;
> }
>
> There is much more details but I would like to present
> the general idea and motivation.
>
> So the motivation is to have the same guarantees of C++ destructor.
> In other words, we have 100% certainty (at compile time) that some
> destructor is called before the end of scope.
>
> Next..level
>
> In C APIs we have some conditional destructors. For instance,
> we need to call fclose only if we have a non null FILE.
>
> So differently than C++ we have a conditional destructor.
> (In C++ the destructor is called unconditionally.)
>
> A workaround is to have a and safe_fclose that also works with
> null. This is how C++ works, they add a if on the destructor.
>
> But a more interesting solution would have a better flow
> analysis and check the conditional creation. But in this case we
> also need to tell the static analysis about this situation.
>
> [[conditional free]] FILE* fopen();
> void fclose([[conditional free]] FILE* p);
>
>
> We we think about the motivation we can see that
> other static analysis are required like ownership transfer.
>
> In general terms what I want is a "automatic review" with similar
> capabilities of human.
> I also want to give the final word to the programmer that at any moment can override the imaginary flag.
>
> The main feedback I would like to have is :
>
> What do you think about the idea of having a 100% compile time
> guarantee that some function is called in C? (Similar of C++ destructor
> guarantee but in this case we need to call it manually)
>

Making it "Mandatory" as in getting a "Required Diagnostic" that makes
the use of the resulting program "Undefinded Behavior" is probably not good.

You WILL need to define what level of analysis the compiler must (and
can) do, and there will be some programs that exceed the ability of the
analysis, or have logic that make the static analysis invalid.

Making it something that the compiler can WARN about, as indicated by
using attributes, is likely a reasonable idea.

Re: destructor for C? (static analisys)

<trf2p9$isp1$1@dont-email.me>

  copy mid

https://www.novabbs.com/devel/article-flat.php?id=24617&group=comp.lang.c++#24617

  copy link   Newsgroups: comp.lang.c
 by: BGB - Thu, 2 Feb 2023 01:14 UTC

On 2/1/2023 4:51 PM, Öö Tiib wrote:
> On Wednesday, 1 February 2023 at 22:05:17 UTC+2, Thiago Adams wrote:
>>
>> The main feedback I would like to have is :
>>
>> What do you think about the idea of having a 100% compile time
>> guarantee that some function is called in C? (Similar of C++ destructor
>> guarantee but in this case we need to call it manually)
>
> What I would like is that tools as Valgrind are made more accessible to
> novices and that everybody are taught to use those tools. That may be
> hard but at least feels worth of any effort, however little.
>

In my project (C compiler + ISA), I do sort-of have things like an
experimental feature for bounds-checked arrays (with partial ISA level
support).

Currently, it only applies to local (stack based) arrays, and to global
arrays. Applying it to malloc or similar (in any transparent way) would
be non-trivial. Could work maybe if one had a special "malloc_array()"
or similar, but otherwise wouldn't really play as nicely with code which
implements custom memory allocators.

One other drawback is that I had to shoe-horn the bounds-check data into
12 bits, which only really allows for approximate bounds checks:
E5.F3 minifloat giving the main array size, and 4-bits giving a
denormalized offset bias (shares the same exponent scale).

Some trickery was used to allow a "LEAT.B" operation which combines a
LEA with updating the bounds-check bias, using a carry-flag out of the
low bits when adjusting the bias.

Some limitations of the scheme though is that it can't effectively
encode out-of-bounds offsets, and requires padding arrays and
bounds-checks slightly to deal with "error".

Otherwise, it is basically invisible to normal C code.
Performance impact seems to be fairly small, though it does have some
cost in terms of code density.

Does have a minor ABI impact in that array pointers originating in code
which has the feature may not necessarily work "flawlessly" if used in
code built without bounds-checks (some operations, such like calculating
the difference between pointers, or performing comparisons, require
stripping off the high-order bits in the bounds-checked case).

Granted, both cases could be partially addressed if there were a
"Subtract but sign extend from the low 48 bits" operation.

> Otherwise neither C nor C++ (nor garbage collection like in C#) does
> guarantee that life-time of objects in program is well managed and
> adding more syntax sugar might only confuse that fact.
>

Automatic management of object lifetime, or cost-effectively determining
this at compile or runtime, is non-trivial. Otherwise, if it were not
non-trivial, garbage collection would effectively be a solved issue.

In one of my own languages, I had partially addressed the issue by
making it semi-explicit:
"new TypeDesc" / "new Class(...)", allocate heap object.
"new! TypeDesc" / "new! Class(...)", allocate with automatic lifetime.
"new(ZoneID) TypeDesc" / "new(ZoneID) Class(...)", allocate within a
given zone.

Formally, this language does not use a garbage collector by default.

In this case, "new!" uses the same underlying mechanism as "alloca()" in
my case, which is in turn implemented by internally allocating stuff on
the heap and adding it to a linked list associated within the current
stack frame (with a callback function to either free the object or to
invoke a destructor).

So, say:
alloca(size);
Becomes, essentially:
__alloca(&alloca_head, size);

And, when the function returns:
__alloca_end(&alloca_head);
Which frees everything in the list.

And, on entry:
__alloca_start(&alloca_head);
Which initializes the list (mostly sets it to NULL).

Where each object essentially has a small hidden header:
{
void *next; //next object in list
void (*doFree)(void *obj); //called to free the object
char data[]; //data area for object
}

Which internally, essentially just calls malloc/free for the backing
memory (where the ABI design in this case only really accommodates
fixed-size stack frames).

For class object types (in my language), this would essentially call its
'delete' handler (which in turn calls the object's destructor method,
and then calls free).

Experimentally, VLAs also exist, and were implemented with the same
underlying mechanism. As can be noted, with this implementation,
manually using malloc/free for the array is generally more efficient.

Or, at least, something to this effect...

Re: destructor for C? (static analisys)

<trg118$qlct$1@dont-email.me>

  copy mid

https://www.novabbs.com/devel/article-flat.php?id=24618&group=comp.lang.c++#24618

  copy link   Newsgroups: comp.lang.c
 by: David Brown - Thu, 2 Feb 2023 09:50 UTC

On 01/02/2023 21:05, Thiago Adams wrote:
> I am working in a static analysis prototype that has the
> mission to detect and emit an warming of missing free/destroy
> operation.
>
> (The idea is general but I am starting with free to explain)
>

<snip>

I like the idea in principle. But I have a general point that I really
think you should consider before fixing the details too much.

If you invent your own syntax that is different from those of other
mainstream tools, your syntax will die away. If your syntax is in
direct conflict with other tools or future C standards, it will die away.

If you are doing something that is basically the same as existing tools,
and you use the same syntax, then people can write more portable code,
and the chances of the syntax becoming standardised are much higher.

In general, your choice of syntax should be either copying existing
extensions when they exist and do a similar job, or thinking carefully
about what fits with planned C standards and what might be realistic for
future standardisation.

So with that in mind, C2x-style attributes are a possibility. But you
should not use un-prefixed attributes, like [[free]]. These are
reserved for future C standards - it is not unlikely that a future C
would implement a similar feature, with attribute names like "malloc"
and "free", but there's no guarantee of identical semantics. Such
incompatibilities are bad - avoid them by using prefixed attributes like
"[[TACC::free]]" that are implementation-specific.

You should also look closely at the "malloc" attribute in gcc (and, I
assume, clang) :

<https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html>

It looks like you are trying to do much the same thing here. You should
therefore think about using the same syntax, for the same purpose.

You should also look at clang's attributes and analysis tools (I am not
very familiar with them as yet).

>
> So the motivation is to have the same guarantees of C++ destructor.
> In other words, we have 100% certainty (at compile time) that some
> destructor is called before the end of scope.
>
> Next..level
>

<snip>

Again, gcc (and clang) have a syntax for doing that. (And it is a
feature I have used a couple of times, so clearly I like the idea.)

Re: destructor for C? (static analisys)

<edb4116b-e4cd-4c0e-85c7-0cb45f82fe7en@googlegroups.com>

  copy mid

https://www.novabbs.com/devel/article-flat.php?id=24619&group=comp.lang.c++#24619

  copy link   Newsgroups: comp.lang.c
 by: Öö Tiib - Thu, 2 Feb 2023 19:01 UTC

On Thursday, 2 February 2023 at 03:14:32 UTC+2, BGB wrote:
> On 2/1/2023 4:51 PM, Öö Tiib wrote:
> > On Wednesday, 1 February 2023 at 22:05:17 UTC+2, Thiago Adams wrote:
> >>
> >> The main feedback I would like to have is :
> >>
> >> What do you think about the idea of having a 100% compile time
> >> guarantee that some function is called in C? (Similar of C++ destructor
> >> guarantee but in this case we need to call it manually)
> >
> > What I would like is that tools as Valgrind are made more accessible to
> > novices and that everybody are taught to use those tools. That may be
> > hard but at least feels worth of any effort, however little.
> >
> In my project (C compiler + ISA), I do sort-of have things like an
> experimental feature for bounds-checked arrays (with partial ISA level
> support).
>
> Currently, it only applies to local (stack based) arrays, and to global
> arrays. Applying it to malloc or similar (in any transparent way) would
> be non-trivial. Could work maybe if one had a special "malloc_array()"
> or similar, but otherwise wouldn't really play as nicely with code which
> implements custom memory allocators.
>
>
> One other drawback is that I had to shoe-horn the bounds-check data into
> 12 bits, which only really allows for approximate bounds checks:
> E5.F3 minifloat giving the main array size, and 4-bits giving a
> denormalized offset bias (shares the same exponent scale).
>
> Some trickery was used to allow a "LEAT.B" operation which combines a
> LEA with updating the bounds-check bias, using a carry-flag out of the
> low bits when adjusting the bias.
>
> Some limitations of the scheme though is that it can't effectively
> encode out-of-bounds offsets, and requires padding arrays and
> bounds-checks slightly to deal with "error".
>
>
>
> Otherwise, it is basically invisible to normal C code.
> Performance impact seems to be fairly small, though it does have some
> cost in terms of code density.
>
> Does have a minor ABI impact in that array pointers originating in code
> which has the feature may not necessarily work "flawlessly" if used in
> code built without bounds-checks (some operations, such like calculating
> the difference between pointers, or performing comparisons, require
> stripping off the high-order bits in the bounds-checked case).
>
> Granted, both cases could be partially addressed if there were a
> "Subtract but sign extend from the low 48 bits" operation.

Maybe yes can encode couple bits in pointer as meta-information and
so gain hardware support to some of it ... but approximate bounds
sound bad. Off-by-one is most common violation of bounds.
Also how can it work with nested object like array in struct in array?

> > Otherwise neither C nor C++ (nor garbage collection like in C#) does
> > guarantee that life-time of objects in program is well managed and
> > adding more syntax sugar might only confuse that fact.
> >
> Automatic management of object lifetime, or cost-effectively determining
> this at compile or runtime, is non-trivial. Otherwise, if it were not
> non-trivial, garbage collection would effectively be a solved issue.
>
>
> In one of my own languages, I had partially addressed the issue by
> making it semi-explicit:
> "new TypeDesc" / "new Class(...)", allocate heap object.
> "new! TypeDesc" / "new! Class(...)", allocate with automatic lifetime.
> "new(ZoneID) TypeDesc" / "new(ZoneID) Class(...)", allocate within a
> given zone.
>
> Formally, this language does not use a garbage collector by default.
>
>
> In this case, "new!" uses the same underlying mechanism as "alloca()" in
> my case, which is in turn implemented by internally allocating stuff on
> the heap and adding it to a linked list associated within the current
> stack frame (with a callback function to either free the object or to
> invoke a destructor).
>
> So, say:
> alloca(size);
> Becomes, essentially:
> __alloca(&alloca_head, size);
>
> And, when the function returns:
> __alloca_end(&alloca_head);
> Which frees everything in the list.
>
> And, on entry:
> __alloca_start(&alloca_head);
> Which initializes the list (mostly sets it to NULL).
>
> Where each object essentially has a small hidden header:
> {
> void *next; //next object in list
> void (*doFree)(void *obj); //called to free the object
> char data[]; //data area for object
> }
>
> Which internally, essentially just calls malloc/free for the backing
> memory (where the ABI design in this case only really accommodates
> fixed-size stack frames).
>
> For class object types (in my language), this would essentially call its
> 'delete' handler (which in turn calls the object's destructor method,
> and then calls free).
>
>
> Experimentally, VLAs also exist, and were implemented with the same
> underlying mechanism. As can be noted, with this implementation,
> manually using malloc/free for the array is generally more efficient.
>
>
> Or, at least, something to this effect...

Sounds a bit like std::unique_ptr of C++ but ... the unique_ptr is not
noticeably different from manual malloc/free in performance.

Re: destructor for C? (static analisys)

<29247092-807d-496e-bc1e-0ee822706510n@googlegroups.com>

  copy mid

https://www.novabbs.com/devel/article-flat.php?id=24620&group=comp.lang.c++#24620

  copy link   Newsgroups: comp.lang.c
 by: Thiago Adams - Thu, 2 Feb 2023 19:59 UTC

On Wednesday, February 1, 2023 at 9:49:43 PM UTC-3, Richard Damon wrote:
> On 2/1/23 3:05 PM, Thiago Adams wrote:
> > I am working in a static analysis prototype that has the
> > mission to detect and emit an warming of missing free/destroy
> > operation.
> >
> > (The idea is general but I am starting with free to explain)
> >
> > The mechanism is like this:
> >
> > We add attribute [[free]] at return type to tell the compiler
> > that someone must call some function that "free" the result before
> > the end of scope.
> >
> > [[free]] malloc(size_t s)
> >
> > Then we use the same attribute in some function argument
> >
> > free([[free]] void * p);
> >
> > int main() {
> > void * p = malloc(10);
> > free(p);
> > }
> >
> > (The name of the function "free" is just a coincidence. It does not need
> > to be the same name of the attribute)
> >
> >
> > The general idea is that declarators (in this case p) have
> > "compile time imaginary flags".
> >
> > So at this line
> >
> > void * p = malloc(10);
> >
> > the compile time imaginary flag "must free" is set on p.
> >
> > calling free(p) the imaginary flag "must free" is cleared.
> >
> > At the end of scope, by design the static analysis will
> > check if the "must free" tag was cleared. It must ensure
> > that it has a path that is always called.
> >
> > If the ownership is transferred we can clear the flag manually.
> > For instance:
> >
> > void * p = malloc(10);
> > void * p2 = p;
> > _clear_imaginary_flag(p, "must free");
> > free(p2);
> >
> > We also can check at compile time these flags:
> >
> > void * p = malloc(10);
> > static_assert(_has_imaginary_flag(p, "must free");
> >
> > To avoid too much annotations some default behavior is useful.
> >
> > For instance, returning a variable with "must free" flag will automatically
> > move the ownership.
> >
> > [[free]] void * F() {
> > void * p = malloc(10);
> > return p;
> > }
> >
> > There is much more details but I would like to present
> > the general idea and motivation.
> >
> > So the motivation is to have the same guarantees of C++ destructor.
> > In other words, we have 100% certainty (at compile time) that some
> > destructor is called before the end of scope.
> >
> > Next..level
> >
> > In C APIs we have some conditional destructors. For instance,
> > we need to call fclose only if we have a non null FILE.
> >
> > So differently than C++ we have a conditional destructor.
> > (In C++ the destructor is called unconditionally.)
> >
> > A workaround is to have a and safe_fclose that also works with
> > null. This is how C++ works, they add a if on the destructor.
> >
> > But a more interesting solution would have a better flow
> > analysis and check the conditional creation. But in this case we
> > also need to tell the static analysis about this situation.
> >
> > [[conditional free]] FILE* fopen();
> > void fclose([[conditional free]] FILE* p);
> >
> >
> > We we think about the motivation we can see that
> > other static analysis are required like ownership transfer.
> >
> > In general terms what I want is a "automatic review" with similar
> > capabilities of human.
> > I also want to give the final word to the programmer that at any moment can override the imaginary flag.
> >
> > The main feedback I would like to have is :
> >
> > What do you think about the idea of having a 100% compile time
> > guarantee that some function is called in C? (Similar of C++ destructor
> > guarantee but in this case we need to call it manually)
> >
> Making it "Mandatory" as in getting a "Required Diagnostic" that makes
> the use of the resulting program "Undefinded Behavior" is probably not good.
>
> You WILL need to define what level of analysis the compiler must (and
> can) do, and there will be some programs that exceed the ability of the
> analysis, or have logic that make the static analysis invalid.
>
> Making it something that the compiler can WARN about, as indicated by
> using attributes, is likely a reasonable idea.

I understand the full guarantees of life time check may be hard to check
and also describe in terns of annotations. I don´t want to impose a way
to write code that fits the checks. Instead of that I want to check common
case automatically and give the programmer the tools to override any unknown state.

The algorithm to check if the "destructor is called" is basically check
if we have a "direct path" "direct flow" from the "free/destroy" function
until the end of scope.

For instance:

int main() {
void * p = malloc(1);
if (condition1) {
free(p);
} }

In this sample we don´t have a "direct path" "direct flow" from
the end of scope of p and free. (because free is inside a condition)

int main() {
void * p = malloc(1);
if (condition1) {
return ;
} free(p);
}

Here we also don´t have a direct path between return and free.
Both sample have warnings.

What makes this check a little harder is to allow fopen case.

FILE *f = fopen();
if (f) {
close(f);
}

because the code is correct without a direct path from fclose
and the end of scope.

The imaginary flag "must free" must be only turned on after checking f != null.
So what I could see is that more static analysis is required if we want
to cover this case.

At this point we can see some differences from C++ destructor.
C++ destructor is called unconditionally. It would be easier to check this
situation and give the same c++ guarantee - that is - some destroy
function is always called before the end of scope.

Another difference c++ dtor cannot be turned off. In C++
even after transferring ownership the destructor of the moved
object is called.

With this "imaginary flags" the need to call a destructor can be turned
off by "removing" automatically or manually the imaginary flag "must free"

For instance
void *p1 = malloc(1);
void *p2 =p1;
_remove_imaginary_flag(p1, "must free");
_add_imaginary_flag(p2, "must free");
free(p2);

(This code is just to show the idea, but the syntax is not practical.)

In rust, in some cases, the compiler cannot determine if the
destructor must be called or not.

string s = "text";
string s2;
if (condition) {
s2 = s1; //in rust attributing is the same of moving
}
I understand in this case rust creates a dynamic flag
that works as condition. This is done by the compiler.
In runtime if s1 is moved this flag is true, "moved".
In C++ the programmer would use some flag or existing state to mark that
at the destructor no job is required.

My implementation is too basic so far, so its is too early to test.
I haven´t started the fopen/fclose case yet.

Apart of "free" I also have a "destroy" flag

struct [[destroy]] X { int i;};
void x_destroy([[destroy]] struct X * px){}

int main() {
struct X x = {0};
} the difference that "must free" imaginary flags are set on
assignment . destroy does not need an assignment the flag
is one after declaration. This is a easy code, no conditional destructor
and very useful.

Re: destructor for C? (static analisys)

<trhp68$14jb9$1@dont-email.me>

  copy mid

https://www.novabbs.com/devel/article-flat.php?id=24621&group=comp.lang.c++#24621

  copy link   Newsgroups: comp.lang.c
 by: BGB - Fri, 3 Feb 2023 01:48 UTC

On 2/2/2023 1:01 PM, Öö Tiib wrote:
> On Thursday, 2 February 2023 at 03:14:32 UTC+2, BGB wrote:
>> On 2/1/2023 4:51 PM, Öö Tiib wrote:
>>> On Wednesday, 1 February 2023 at 22:05:17 UTC+2, Thiago Adams wrote:
>>>>
>>>> The main feedback I would like to have is :
>>>>
>>>> What do you think about the idea of having a 100% compile time
>>>> guarantee that some function is called in C? (Similar of C++ destructor
>>>> guarantee but in this case we need to call it manually)
>>>
>>> What I would like is that tools as Valgrind are made more accessible to
>>> novices and that everybody are taught to use those tools. That may be
>>> hard but at least feels worth of any effort, however little.
>>>
>> In my project (C compiler + ISA), I do sort-of have things like an
>> experimental feature for bounds-checked arrays (with partial ISA level
>> support).
>>
>> Currently, it only applies to local (stack based) arrays, and to global
>> arrays. Applying it to malloc or similar (in any transparent way) would
>> be non-trivial. Could work maybe if one had a special "malloc_array()"
>> or similar, but otherwise wouldn't really play as nicely with code which
>> implements custom memory allocators.
>>
>>
>> One other drawback is that I had to shoe-horn the bounds-check data into
>> 12 bits, which only really allows for approximate bounds checks:
>> E5.F3 minifloat giving the main array size, and 4-bits giving a
>> denormalized offset bias (shares the same exponent scale).
>>
>> Some trickery was used to allow a "LEAT.B" operation which combines a
>> LEA with updating the bounds-check bias, using a carry-flag out of the
>> low bits when adjusting the bias.
>>
>> Some limitations of the scheme though is that it can't effectively
>> encode out-of-bounds offsets, and requires padding arrays and
>> bounds-checks slightly to deal with "error".
>>
>>
>>
>> Otherwise, it is basically invisible to normal C code.
>> Performance impact seems to be fairly small, though it does have some
>> cost in terms of code density.
>>
>> Does have a minor ABI impact in that array pointers originating in code
>> which has the feature may not necessarily work "flawlessly" if used in
>> code built without bounds-checks (some operations, such like calculating
>> the difference between pointers, or performing comparisons, require
>> stripping off the high-order bits in the bounds-checked case).
>>
>> Granted, both cases could be partially addressed if there were a
>> "Subtract but sign extend from the low 48 bits" operation.
>
> Maybe yes can encode couple bits in pointer as meta-information and
> so gain hardware support to some of it ... but approximate bounds
> sound bad. Off-by-one is most common violation of bounds.
> Also how can it work with nested object like array in struct in array?
>

It doesn't really work for off-by-1, since by the time bounds are tight
enough to detect an off-by-1, they are also tight enough to get
false-positive bounds failures.

For stack and global arrays, it pads the array slightly, such that
"known good" accesses will not give false-positives, and such that
"slightly out of bounds" accesses should not hit into any other memory
(so, arrays are padded with a small "no man's land" area).

This doesn't really work for structs though.
Currently, arrays within structs are not bounds-checked.

Though, the scheme would handle this, as its only "real" requirement for
this is that the bounds are known at compile time. However, if the
bounds are padded, it would not reliably detect out-of-bounds access.

For my other language, the idea will be that the bounds will not be
padded, in which case the bounds-check will fall back to a handler which
will either verify that the access is actually valid, or raise an
exception (in C mode, if the initial check fails, it immediately
generates a fault).

But, as noted, there are limits to what I can do with a 64-bit pointer
size. More accurate bounds checks would require using 128-bit pointers,
which have a much bigger set of drawbacks.

It does generally help detect cases where things go "clearly out of
bounds" though. But, sadly, doesn't do as much to help detect cases
where the compiler itself is buggy.

And, is at least, "better than nothing".
It is generally used alongside stack canaries and some other features.

>>> Otherwise neither C nor C++ (nor garbage collection like in C#) does
>>> guarantee that life-time of objects in program is well managed and
>>> adding more syntax sugar might only confuse that fact.
>>>
>> Automatic management of object lifetime, or cost-effectively determining
>> this at compile or runtime, is non-trivial. Otherwise, if it were not
>> non-trivial, garbage collection would effectively be a solved issue.
>>
>>
>> In one of my own languages, I had partially addressed the issue by
>> making it semi-explicit:
>> "new TypeDesc" / "new Class(...)", allocate heap object.
>> "new! TypeDesc" / "new! Class(...)", allocate with automatic lifetime.
>> "new(ZoneID) TypeDesc" / "new(ZoneID) Class(...)", allocate within a
>> given zone.
>>
>> Formally, this language does not use a garbage collector by default.
>>
>>
>> In this case, "new!" uses the same underlying mechanism as "alloca()" in
>> my case, which is in turn implemented by internally allocating stuff on
>> the heap and adding it to a linked list associated within the current
>> stack frame (with a callback function to either free the object or to
>> invoke a destructor).
>>
>> So, say:
>> alloca(size);
>> Becomes, essentially:
>> __alloca(&alloca_head, size);
>>
>> And, when the function returns:
>> __alloca_end(&alloca_head);
>> Which frees everything in the list.
>>
>> And, on entry:
>> __alloca_start(&alloca_head);
>> Which initializes the list (mostly sets it to NULL).
>>
>> Where each object essentially has a small hidden header:
>> {
>> void *next; //next object in list
>> void (*doFree)(void *obj); //called to free the object
>> char data[]; //data area for object
>> }
>>
>> Which internally, essentially just calls malloc/free for the backing
>> memory (where the ABI design in this case only really accommodates
>> fixed-size stack frames).
>>
>> For class object types (in my language), this would essentially call its
>> 'delete' handler (which in turn calls the object's destructor method,
>> and then calls free).
>>
>>
>> Experimentally, VLAs also exist, and were implemented with the same
>> underlying mechanism. As can be noted, with this implementation,
>> manually using malloc/free for the array is generally more efficient.
>>
>>
>> Or, at least, something to this effect...
>
> Sounds a bit like std::unique_ptr of C++ but ... the unique_ptr is not
> noticeably different from manual malloc/free in performance.

Dunno.

I don't really have a full C++ implementation in BGBCC (and none of the
C++ standard library).

So, in this case, it is basically a mechanism built into the compiler,
and used to implement "alloca", VLAs, lambdas, and other objects with an
automatic lifetime.

Mechanism and behavior is slightly different from the C++ RAII mechanism
though, in that it deals with "any automatic allocations within the
scope of a function" (in a linked list sense), whereas RAII deals with
it per-object and for each block-scope.

So, say:
if(cond)
{
Foo a();
...
}
Would turn into something like, say:
if(cond)
{
Foo a;
Foo::ctor(&a);
...
Foo::dtor(&a);
}

But, this is not exactly how my alloca mechanism works (with all
destructor calls being delayed until the point where the parent function
returns).

My other language (BS2) has a "zone" system, which operates in a way
vaguely influenced by the "Z_Malloc" system in the Doom engine.

It differs in a few points though:
Z_Malloc uses 8-bit ZoneID's, but this uses 16-bit;
Z_Malloc uses relative comparison for ClearZone, but mine uses bit-mask
and equality ("(zoneid&zonemask)==target", object gets freed).

In my language, destroying an object with a ClearZone may also trigger
any destructors/finalizers to trigger.


Click here to read the complete article
Re: destructor for C? (static analisys)

<7f8b7ba5-d861-4467-88f1-13fad488bb52n@googlegroups.com>

  copy mid

https://www.novabbs.com/devel/article-flat.php?id=24636&group=comp.lang.c++#24636

  copy link   Newsgroups: comp.lang.c
 by: Öö Tiib - Sun, 5 Feb 2023 15:10 UTC

On Friday, 3 February 2023 at 03:49:12 UTC+2, BGB wrote:
> On 2/2/2023 1:01 PM, Öö Tiib wrote:
> > On Thursday, 2 February 2023 at 03:14:32 UTC+2, BGB wrote:
> >> On 2/1/2023 4:51 PM, Öö Tiib wrote:
>
> And, is at least, "better than nothing".
>
> It is generally used alongside stack canaries and some other features.
>
Yes. If it is very low cost and better than nothing then it is worth having
anyway, just that it can't replace Valgrind usage.

> >> Or, at least, something to this effect...
> >
> > Sounds a bit like std::unique_ptr of C++ but ... the unique_ptr is not
> > noticeably different from manual malloc/free in performance.
> Dunno.
>
> I don't really have a full C++ implementation in BGBCC (and none of the
> C++ standard library).
>
But why don't you make LLVM compiler/linker? It does not mean you will
get full Ada, C, C++, D, Delphi, Fortran, Haskell, Julia, Objective-C, Rust,
and Swift without significant sweating over standard libraries but you
will get some.

Most good about well established languages like C++ for me is the tools
and libraries. I can add some tools like clang-format clang-tidy cppcheck
etc. to toolchain and I can port some libraries instead of writing those.

1
server_pubkey.txt

rocksolid light 0.9.8
clearnet tor