Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Re: No, C is not a simple language

348 views
Skip to first unread message

Manfred

unread,
Apr 17, 2021, 1:12:52 PM4/17/21
to
(cross-posting to comp.lang.c to let the heat fire up)

On 4/17/2021 7:56 AM, Juha Nieminen wrote:
> I know that this is probably going to piss off avid C programmers,
> but I don't really care all that much.

Excellent start

[...]
>
> I myself happen to know both languages quite well, and in fact I
> program in both languages as my profession, and I would say that
> I am very knowledgeable and proficient in both languages and have
> no trouble in developing in either one. I am of the strong opinion
> that no, C is not a "simple" language, and that C++ makes so many
> things so much simpler, easier and safer.
>
> I was reminded of this in a quite concrete manner recently when I was
> trying to help a beginner programmer who had decided to try his hands
> on C, and wanted to create a simple text editor, which would read a
> text file, allow the user to edit the text in several ways, and to
> save the file.
>
> It quite quickly turned very frustrating for *both* of us ...

The first comment that comes to mind is that probably part of the
frustration stems from your approach (no offense) to try and use in C a
design that would be suitable for C++ instead. Despite their name, the
two are quite different languages. The similarities are about their
runtime footprint, not about how they are used.

For example, at a first glance the design of string object is not that
great for C.
A common pattern that pops to the mind is to use opaque structs (see
e.g. FILE*), and have something like:

typedef struct string_t STRING;

STRING* alloc_string(); // creates the empty string
STRING* set_string(STRING* s, const char* value);
void free_string(STRING* s);
STRING* concat_string(STRING* s1, STRING* s2);
...

This way there would be no ambiguity about how the STRING object has to
be used.
And yes, in C you have to do all yourself - C is a low level language,
and it is meant for low level stuff. It is hardly the right tool for
anything meant to interface with a human user - it is (still) great for
system programming and back-end processing, IMO.

As second, I agree with Jacob that a text editor is a simple tool, but
it is not easy to make expecially for a beginner.


[snip]

silas poulson

unread,
Apr 17, 2021, 9:33:20 PM4/17/21
to
On Saturday, 17 April 2021 at 18:12:52 UTC+1, Manfred wrote:
> (cross-posting to comp.lang.c to let the heat fire up)

> As second, I agree with Jacob that a text editor is a simple tool, but
> it is not easy to make expecially for a beginner.

For those wanting assistance, Kernighan and Plauger's *Software Tools*
present a Pascal version of the venerable ED

Silas

Lew Pitcher

unread,
Apr 17, 2021, 10:11:06 PM4/17/21
to
Only in /some/ editions. The 1976 edition in my bookcase presents all code
examples in RatFor ("Rational Fortran", a C-like language transpiled into
Fortran).

I believe that, as language fads came and went, K&P updated "Software Tools"
to reflect the (then) current "language-of-choice".

> of the venerable ED
>
> Silas




--
Lew Pitcher
"In Skills, We Trust"

silas poulson

unread,
Apr 18, 2021, 1:35:23 AM4/18/21
to
On Sunday, 18 April 2021 at 03:11:06 UTC+1, Lew Pitcher wrote:
> examples in RatFor ("Rational Fortran", a C-like language transpiled into
> Fortran).

Indeed, forgot my Pascal version was the re-release.

> I believe that, as language fads came and went, K&P updated "Software Tools"
> to reflect the (then) current "language-of-choice".

Only once!
The Pascal rewrite lead to Kernighan's (in)famous
*Why Pascal is Not My Favorite Programming Language* commentary

Silas

Real Troll

unread,
Apr 18, 2021, 12:17:46 PM4/18/21
to
On 18/04/2021 02:33, silas poulson wrote:
>
> For those wanting assistance, Kernighan and Plauger's *Software Tools*
> present a Pascal version of the venerable ED
>

It's a silly suggestion from Silas (no pun intended!). We are in 2021
and people should try to use latest books. Technology has moved on and
no one compiler is good in the 21st century and therefore archaic books
are for academic research and not for serious programming work.


Juha Nieminen

unread,
Apr 18, 2021, 12:47:08 PM4/18/21
to
In comp.lang.c++ Manfred <non...@add.invalid> wrote:
> For example, at a first glance the design of string object is not that
> great for C.
> A common pattern that pops to the mind is to use opaque structs (see
> e.g. FILE*), and have something like:
>
> typedef struct string_t STRING;
>
> STRING* alloc_string(); // creates the empty string
> STRING* set_string(STRING* s, const char* value);
> void free_string(STRING* s);
> STRING* concat_string(STRING* s1, STRING* s2);
> ...
>
> This way there would be no ambiguity about how the STRING object has to
> be used.

I see no practical difference between that and a String struct. The usage is
pretty much the same, and all the same problems appear (such as assigning
one string object/pointer to another requiring extra care to avoid destroying
the string twice or accessing a destroyed string).

Manfred

unread,
Apr 18, 2021, 2:31:04 PM4/18/21
to
If the struct is opaque, the compiler won't allow you to assign a STRING
object to another, as a start. In fact it won't allow to perform /any/
operation on the object, except through the API that you define.

You can still mess up with pointers, but if you want to use C, then you
have to know better.

This is nothing new, as I wrote the entire FILE* infrastructure works
this way.

Keith Thompson

unread,
Apr 18, 2021, 3:46:20 PM4/18/21
to
Except that you *can* assign objects of type FILE (if you don't mind
undefined behavior). The FILE type is opaque in the sense that the
standard doesn't say what it looks like, but it has to be an object
type.

--
Keith Thompson (The_Other_Keith) Keith.S.T...@gmail.com
Working, but not speaking, for Philips Healthcare
void Void(void) { Void(); } /* The recursive call of the void */

jacobnavia

unread,
Apr 18, 2021, 3:52:30 PM4/18/21
to
Dear real troll

You make honor to your pseudo. We are NOT speaking about real
programming WORK but a project for a beginner. See the posts before this
one,specially the first one where Mr Nieminen explains the context.

In THAT context, the answer of Mr Silas is prefectly appropiate. ed is a
project where you can begin honing your c skills.

Manfred

unread,
Apr 18, 2021, 4:53:49 PM4/18/21
to
The relevant part was:

> typedef struct string_t STRING;

I am talking about opaque types, which are incomplete and as such you
can't use objects of their type.

As far as the standard is concerned FILE is an opaque type whose
definition might not be seen (and in fact is _not_ intended to be seen,
let alone used) by user code.
I think it is clear that the fact that in some implementations the
definition of FILE might percolate into user TUs is orthogonal to this
discussion.

$ cat opaque.c
typedef struct string_t STRING;

STRING* string_concat(STRING* s1, STRING* s2);
void string_free(STRING* s);

void foo(STRING* s1, STRING* s2)
{
STRING* s3 = string_concat(s1, s2);

string_free(s3);
}

void bar(STRING* s1, STRING* s2)
{
*s2 = *s1; // can't do that
}

$ cc -std=c11 -Wall -pedantic -c opaque.c
opaque.c: In function ‘bar’:
opaque.c:17:9: error: invalid use of incomplete typedef ‘STRING’ {aka
‘struct string_t’}
17 | *s2 = *s1;
| ^
opaque.c:17:7: error: invalid use of incomplete typedef ‘STRING’ {aka
‘struct string_t’}
17 | *s2 = *s1;
| ^

Juha Nieminen

unread,
Apr 19, 2021, 3:18:26 AM4/19/21
to
In comp.lang.c++ Manfred <non...@add.invalid> wrote:
> If the struct is opaque, the compiler won't allow you to assign a STRING
> object to another, as a start. In fact it won't allow to perform /any/
> operation on the object, except through the API that you define.

I see no difference between your scheme and mine.

String str1 = String_with_cstr("hello");
String str2 = str1;
// lots of code here
String_free(str1);
String_free(str2); // oops

vs.

String* str1 = String_with_c_str("hello");
String* str2 = str1;
// lots of code here
String_free(str1);
String_free(str2); // oops

> You can still mess up with pointers, but if you want to use C, then you
> have to know better.

My point is that for a beginner it's so much simpler to be able to do:

std::string str1 = "hello";
std::string str2 = str1;
// lots of code here
// no need to even destroy those strings, they'll be destroyed
// automatically

It's also significantly easier to try to guide a beginner to create a small
project like I described.

(Sure, you can start arguing about the efficiency issues of std::string
always making a deep copy whenever it's passed around, but this is just
trying to guide a beginner to do one of his first projects. The efficiency
questions can come up much, much later in the learning process. At this
point the efficiency issues are completely irrelevant.)

Tim Rentsch

unread,
Apr 19, 2021, 7:20:31 AM4/19/21
to
Keith Thompson <Keith.S.T...@gmail.com> writes:

> Manfred <non...@add.invalid> writes:
>> [...]

[discussion of opaque types using pointers to structs whose contents
are not defined]

>> This is nothing new, as I wrote the entire FILE* infrastructure
>> works this way.
>
> Except that you *can* assign objects of type FILE (if you don't
> mind undefined behavior). The FILE type is opaque in the sense
> that the standard doesn't say what it looks like, but it has to be
> an object type.

It should be noted that the term "object type" acquired a new
meaning in C11. Before C11, the C standard divides types into
function types, object types, and incomplete types, so "object
type" always meant a complete type and not an incomplete type.
Starting in C11, the C standard divides types into function types
and object types, with object types further divided into complete
object types and incomplete object types. So although it is true
that FILE is an object type, starting in C11 that doesn't mean it
is necessarily a complete object type.

Manfred

unread,
Apr 19, 2021, 11:15:33 AM4/19/21
to
On 4/19/2021 9:18 AM, Juha Nieminen wrote:
> In comp.lang.c++ Manfred <non...@add.invalid> wrote:
>> If the struct is opaque, the compiler won't allow you to assign a STRING
>> object to another, as a start. In fact it won't allow to perform /any/
>> operation on the object, except through the API that you define.
>
> I see no difference between your scheme and mine.
>
> String str1 = String_with_cstr("hello");
> String str2 = str1;
> // lots of code here
> String_free(str1);
> String_free(str2); // oops
>
> vs.
>
> String* str1 = String_with_c_str("hello");
> String* str2 = str1;
> // lots of code here
> String_free(str1);
> String_free(str2); // oops

I do see a difference there, and it is the tiny little '*' in
> String* str2 = str1;

Which means that you are duplicating a pointer to an object and this
should trigger extra attention wrt object lifetime (and other things for
that matter).
Would you ever write with any lightness:
FILE* f1 = fopen("foo", "r");
FILE* f2 = f1;
// ...

If you want to use C you should get trained to this kind of details.
However, I get that this is probably what you mean by C being not simple
- it is not.
My point is that if you are using C, and you are familiar with it, then
there is a difference between the two designs.

(An example where your design is perfectly OK and widely used:
typedef struct point_t
{
int x;
int y;
} POINT;

POINT p1 = {42, 42};
POINT p2 = p1;
...

But here you make sure that the type is safely (trivially) copyable, a.o.)

>
>> You can still mess up with pointers, but if you want to use C, then you
>> have to know better.
>
> My point is that for a beginner it's so much simpler to be able to do:
>
> std::string str1 = "hello";
> std::string str2 = str1;
> // lots of code here
> // no need to even destroy those strings, they'll be destroyed
> // automatically
>
> It's also significantly easier to try to guide a beginner to create a small
> project like I described.

I'd comment about the term "easy" - you still need to know how RAII and
stuff works here, even if it all happens automatically.
I'd say that the fact that it happens behind the scenes might even make
it harder for a beginner.
The code /looks/ simpler, the mechanics of it is not - C++ is not simple
either.

Considering C++ simple is a major source of mistakes, IMO.

>
> (Sure, you can start arguing about the efficiency issues of std::string
> always making a deep copy whenever it's passed around, but this is just
> trying to guide a beginner to do one of his first projects. The efficiency
> questions can come up much, much later in the learning process. At this
> point the efficiency issues are completely irrelevant.)
>

Agreed, performance has no relevance here.

Thiago Adams

unread,
Apr 19, 2021, 11:29:37 AM4/19/21
to
Last week I receive a warning in clang complaining about
converting void* to function pointer.
Searching for this error I found a explanation that C99 or C11 I don't
recall makes this distinction and this conversion is not guaranteed.
The solution proposed on stack overflow was storing a dummy function
pointer instead of void*, lets say void (PF)(void) and then cast this dummy
function pointer to the final type.
I decided just ignore the warning in my code.
The justification was that void* could have different size than function pointers.


Keith Thompson

unread,
Apr 19, 2021, 4:21:28 PM4/19/21
to
C does not define the behavior of converting between object pointer
types and function pointer types (except for the special case of a null
pointer constant). I believe that's been the case since C90.

The section of the standard on pointer conversions just omits any
mention of conversions between object pointer types and function pointer
types. The section on cast operators, however, doesn't say that such a
conversion is a constraint violation. I find this odd. (I think we had
a long and inconclusive discussion about this a while ago.)

It does allow conversions of function pointer types to and from
integers, and integers to and from object pointer types, but the results
are implementation-defined in most cases.

And yes, the rationale is that void* has to be big enough to hold any
object pointer value, but function pointers might be bigger than object
pointers (more precisely, they might not be convertible without loss of
information).

Richard Damon

unread,
Apr 19, 2021, 6:04:09 PM4/19/21
to
On 4/19/21 4:21 PM, Keith Thompson wrote:
It isn't made a constraint violation because in many cases it does work
and being able to do it is useful.

It isn't defined to work, because on some machines it just can't be done
(especially if they are different sizes or to different memory spaces
like a Harvard Machine)

Bart

unread,
Apr 19, 2021, 7:50:33 PM4/19/21
to
You can always make it work. On any machine where a raw function pointer
is very different from an object pointer (say 256 bits vs, 64 bits),
then a C compiler can implement such pointers via object pointers.

In practice, for every actual function, there is an accompanying 256-bit
value, in data memory, containing its address. A 'function pointer' as a
C program will see it, is a 64-bit pointer to that 256-bit object.

The compiler will take care of the double-dereference needed to call
such a function.

However cross-language libraries would need to cooperate on such a
scheme to pass such function pointers via APIs.

Juha Nieminen

unread,
Apr 20, 2021, 12:05:39 AM4/20/21
to
In comp.lang.c++ Manfred <non...@add.invalid> wrote:
>> My point is that for a beginner it's so much simpler to be able to do:
>>
>> std::string str1 = "hello";
>> std::string str2 = str1;
>> // lots of code here
>> // no need to even destroy those strings, they'll be destroyed
>> // automatically
>>
>> It's also significantly easier to try to guide a beginner to create a small
>> project like I described.
>
> I'd comment about the term "easy" - you still need to know how RAII and
> stuff works here, even if it all happens automatically.
> I'd say that the fact that it happens behind the scenes might even make
> it harder for a beginner.

Might make *what* harder for a beginner, exactly?

Chris M. Thomasson

unread,
Apr 20, 2021, 12:42:26 AM4/20/21
to
I would not call C easy. Instead, I would refer to it as being more to
"the point", perhaps? Things have to be "explicit", it can be more
verbose. I say that because C does not have RAII. Also, C is really nice
to create plugins wrt creating portable bindings to other languages.
Usually in C, things tend go like the following when coding up a new
"object", so to speak:

Typing in the newsreader, sorry for typos.
________________________________

#include <stdio.h>
#include <stdlib.h>


struct foo
{
int a;
void* mem;
};

int foo_create(struct foo* const self)
{
if ((self->mem = malloc(sizeof(42))))
{
self->a = 84;
printf("foo_create(%p)\n", (void*)self);
return 1;
}

return 0;
}

void foo_destroy(struct foo const* const self)
{
printf("foo_destroy(%p)\n", (void*)self);
free(self->mem);
}

void foo_output(struct foo const* self, FILE* output)
{
printf("foo_output(%p)\n", (void*)self);
fprintf(output, "struct foo(%p)->a = %d\n", (void*)self, self->a);
fprintf(output, "struct foo(%p)->mem = %p\n", (void*)self, self->mem);
}


int main()
{
struct foo foo;

if (foo_create(&foo))
{
foo_output(&foo, stdout);

foo_destroy(&foo);
}

return 0;
}
________________________________


See how I have to make an explicit call to foo_destroy? This can be
handled in C++ in a "cleaner/safer" manner. Again, typing in the
newsreader sorry for typos. This would be a simple C++ wrapper to the C
API above:
________________________________

struct foo_wrapper
{
struct foo m_foo;

foo_wrapper() { if (! foo_create(&m_foo)) throw; }
~foo_wrapper() { foo_destroy(&m_foo); }

void output(FILE* output_) const
{
foo_output(&m_foo, output_);
}
};

________________________________

Then we can go:

{
foo_wrapper foo;
foo.output(stdout);
}

There. If it fails to create a foo in foo_create, it throws. Otherwise,
foo_destroy is guaranteed to be called when foo goes out of scope. So,
C++ can be more convenient, and safer. No need to use all of C++, but it
does have its moments.

David Brown

unread,
Apr 20, 2021, 2:32:25 AM4/20/21
to
Such schemes are possible, and sometimes used in practice. (They have
been used on the AVR - my usual go-to choice for a somewhat awkward
architecture.) But I've also seen microcontrollers where function
pointers are bigger than data pointers.

Cooperation between languages or tools /always/ requires a common ABI -
there is no special case here.



Richard Damon

unread,
Apr 20, 2021, 7:18:12 AM4/20/21
to
Yes, you can make it work at a cost, perhaps heavy cost, and C tries not
to make implementation do that sort of heavy cost.

Also, on some machines, like those with a Harvard Architecture, using a
data pointer can NEVER get you the contents at the function pointer, as
you need to use different instructions to access the memory contents in
instruction space than in data space. And the word width might even be
different too.

David Brown

unread,
Apr 20, 2021, 8:25:19 AM4/20/21
to
The way this is typically done (at least as I have seen it) is that when
you take the address of a function that needs a long address (perhaps
even a bank switch), the compiler puts a "far-jump real_foo" instruction
at a low flash addresses (or unbanked memory) and uses that as the
address of the function "foo". That way your main code can call or jump
to "foo" using a short address, while the real code lies elsewhere. The
overhead is then just one extra branch instruction. (Well, sometimes it
is more if the architecture has far-call and far-return instructions.)

Bart

unread,
Apr 20, 2021, 9:09:53 AM4/20/21
to
Can C use function pointers on such architectures at all? In so, it can
work like this:

#include <stdio.h>

void (*F)(int,int); // points to instruction memory
void (**G)(int,int) = &F; // points to data memory

int main(void) {

F(10,20);
(*G)(10,20);

void* p;

p=F;
F=p;
p=G;
G=p;
}

F is a regular function pointer. G is an object pointer with an extra
level of indirection to the same function as F points to.

The assignments between p and F can generate warnings about ISO C
forbidding such conversions, even when you use casts.

But those between p and G are fine.

The proposal would be for the compiler to transparently implement
function pointers like G in my example rather than F. That means being
able to drop, in the source code, the extra * and & associated with G
(the compiler will add them).

> a data pointer can NEVER get you the contents at the function pointer

We're not trying to read the memory that the function pointer points to,
just execute it via a call.


Now, there might be a problem if you can't load an address from data
memory, which is the address of instruction memory. Well, obviously you
can, but I mean using it as an instruction memory.

For example, if there are separate sets of registers than can be loaded
from different kinds of memory; you can't move data between them; and
you can only call a function indirectly from the special instruction
registers.

The answer then is to quickly move on to a saner architecture. However,
since on virtually all actual architectures, a workaround as I outlined
should be possible, then I think it was unnecessary for ISO C to ban
outright conversions between object and function pointers. At least,
they should have been implementation-defined.


(I'm currently working on a new interpreter where a function pointer
points neither into data, nor into instructions (the byte-code
sequence), but is a reference into a symbol table.

Calling the function involves picking up the 'address' of the function
(pointer to the entry-point within the bytecode), from the symbol table
entry. But here the function 'pointer' can also pick up the name and
other info about the function.)

Richard Damon

unread,
Apr 20, 2021, 10:19:12 AM4/20/21
to
On 4/20/21 9:09 AM, Bart wrote:
>  On 20/04/2021 12:17, Richard Damon wrote:
>> On 4/19/21 7:50 PM, Bart wrote:
>
>>> You can always make it work. On any machine where a raw function pointer
>>> is very different from an object pointer (say 256 bits vs, 64 bits),
>>> then a C compiler can implement such pointers via object pointers.
>>>
>>> In practice, for every actual function, there is an accompanying 256-bit
>>> value, in data memory, containing its address. A 'function pointer' as a
>>> C program will see it, is a 64-bit pointer to that 256-bit object.
>>>
>>> The compiler will take care of the double-dereference needed to call
>>> such a function.
>>>
>>> However cross-language libraries would need to cooperate on such a
>>> scheme to pass such function pointers via APIs.
>>
>> Yes, you can make it work at a cost, perhaps heavy cost, and C tries not
>> to make implementation do that sort of heavy cost.
>>
>> Also, on some machines, like those with a Harvard Architecture, using a
>> data pointer can NEVER get you the contents at the function pointer, as
>> you need to use different instructions to access the memory contents in
>> instruction space than in data space. And the word width might even be
>> different too.
>>
>
> Can C use function pointers on such architectures at all? In so, it can
> work like this:

Yes, Function pointers just are different sizes that the object pointer.
>
>  #include <stdio.h>
>
>  void (*F)(int,int);         // points to instruction memory
>  void (**G)(int,int) = &F;   // points to data memory
>
>  int main(void) {
>
>      F(10,20);
>      (*G)(10,20);
>
>      void* p;
>
>      p=F;
>      F=p;
>      p=G;
>      G=p;
>  }
>
> F is a regular function pointer. G is an object pointer with an extra
> level of indirection to the same function as F points to.
>
> The assignments between p and F can generate warnings about ISO C
> forbidding such conversions, even when you use casts.

Yes, because a void* object pointer may not have the capacity to store a
function pointer.
>
> But those between p and G are fine.
>
> The proposal would be for the compiler to transparently implement
> function pointers like G in my example rather than F. That means being
> able to drop, in the source code, the extra * and & associated with G
> (the compiler will add them).

Yes, to make function pointer to data pointer 'work' you may need to
create a thunk for every function that has its address taken. That is a
lot of work, and may require a smart linker to avoid overloading with
duplicates.
>
>> a data pointer can NEVER get you the contents at the function pointer
>
> We're not trying to read the memory that the function pointer points to,
> just execute it via a call.
>

Yes, but some may assume it will.

>
> Now, there might be a problem if you can't load an address from data
> memory, which is the address of instruction memory. Well, obviously you
> can, but I mean using it as an instruction memory.
>
> For example, if there are separate sets of registers than can be loaded
> from different kinds of memory; you can't move data between them; and
> you can only call a function indirectly from the special instruction
> registers.
>
> The answer then is to quickly move on to a saner architecture. However,
> since on virtually all actual architectures, a workaround as I outlined
> should be possible, then I think it was unnecessary for ISO C to ban
> outright conversions between object and function pointers. At least,
> they should have been implementation-defined.
>

It isn't that the work around can't work, it is that the standard does
not require that all such architecture go to that effort.

I suspect that one key reason is that when C was being developed, there
was little need to enforce this behavior on machines where it didn't
just work. It has NEVER been to goal of the standard to make it possible
to write all programs as strictly conforming programs.

James Kuyper

unread,
Apr 20, 2021, 11:50:59 AM4/20/21
to
On 4/20/21 7:17 AM, Richard Damon wrote:
> On 4/19/21 7:50 PM, Bart wrote:
>> On 19/04/2021 23:03, Richard Damon wrote:
>>> On 4/19/21 4:21 PM, Keith Thompson wrote:
>>>> Thiago Adams <thiago...@gmail.com> writes:
...
>>> It isn't defined to work, because on some machines it just can't be done
>>> (especially if they are different sizes or to different memory spaces
>>> like a Harvard Machine)
>>>
>>
>> You can always make it work. On any machine where a raw function pointer
>> is very different from an object pointer (say 256 bits vs, 64 bits),
>> then a C compiler can implement such pointers via object pointers.
>>
>> In practice, for every actual function, there is an accompanying 256-bit
>> value, in data memory, containing its address. A 'function pointer' as a
>> C program will see it, is a 64-bit pointer to that 256-bit object.
>>
>> The compiler will take care of the double-dereference needed to call
>> such a function.
>>
>> However cross-language libraries would need to cooperate on such a
>> scheme to pass such function pointers via APIs.
>
> Yes, you can make it work at a cost, perhaps heavy cost, and C tries not
> to make implementation do that sort of heavy cost.
>
> Also, on some machines, like those with a Harvard Architecture, using a
> data pointer can NEVER get you the contents at the function pointer, as
> you need to use different instructions to access the memory contents in
> instruction space than in data space. And the word width might even be
> different too.

I'm not sure why you think that's relevant. Surely C function pointers
can be implemented on such a machine? If so, then you should be able to
do what he suggests: what is a function pointer as far as C code is
concerned is implemented internally as a data pointer pointing at a
function pointer. Keep in mind that he's responding to a claim that "it
just can't be done", not a claim that "it would be excessively
inconvenient".

Richard Damon

unread,
Apr 20, 2021, 12:06:17 PM4/20/21
to
Yes, it CAN be done, but the Standard doesn't require an implementation
to do so, as the system tools might not implement enough functionality
to do so efficiently.

The second comment is perhaps irrelevant, but sometimes people WILL cast
a function pointer to a char* pointer to get access to the machine
linguage of a function, which might just not work.


Manfred

unread,
Apr 20, 2021, 12:27:33 PM4/20/21
to
Anything that happens behind the scenes and the compiler "does it for
you" does require knowledge by the programmer about something they don't
see and yet they must be aware of and know how it works.
And yes, they are intended to (and actually do) make life easier for the
programmer /after/ they master their mechanics, but a beginner's mistake
related with such hidden features is likely to lead to somewhat
shady/obscure bugs - i.e. hard for said beginner.

On a different perspective, consider e.g. rvalue refs and "universal"
refs (in Meyers' terms) - they make a useful feature, but good luck
explaining it to said beginner.

Juha Nieminen

unread,
Apr 20, 2021, 12:41:41 PM4/20/21
to
In comp.lang.c++ Manfred <non...@add.invalid> wrote:
> Anything that happens behind the scenes and the compiler "does it for
> you" does require knowledge by the programmer about something they don't
> see and yet they must be aware of and know how it works.
> And yes, they are intended to (and actually do) make life easier for the
> programmer /after/ they master their mechanics, but a beginner's mistake
> related with such hidden features is likely to lead to somewhat
> shady/obscure bugs - i.e. hard for said beginner.

The problem with C is that anything that requires manual construction and
destruction will "contaminate" (can't think of a better word) anything that
uses that first thing, with the same requirements. All the way down.

If you have this kind of "string" object that requires manual construction
and destruction, and you put it as a member of a struct, suddenly that
struct "inherits" the same requirement. Use that struct in yet another
struct and that, too, will "inherit" the same requirement.

Moreover, suppose you have a complex hierarchy of structs as members of
other structs, no dynamic memory allocation in sight so far. Then you need
to add dynamic memory allocation to one of the structs (that's being used
as a member of other structs etc) and suddenly all that code breaks. If
there are millions of lines of code using any of those structs that have
now "inherited" the need for manual construction and destruction (even though
they were never designed for that initially), you'll have to go and modify
those millions of lines to follow suit.

The complexity can quickly grow exponentially.

Not so in C++, if you design your structs/classes correctly. Even if some
struct/class very deep in the dependency tree suddenly starts allocating
memory dynamically, that doesn't matter. None of the other structs/classes
that may be using it as their members need to be touched in any way. The
struct/class automatically takes care of its own memory management. You
don't need to go through the millions of lines of code to add support.

> On a different perspective, consider e.g. rvalue refs and "universal"
> refs (in Meyers' terms) - they make a useful feature, but good luck
> explaining it to said beginner.

I wouldn't teach function and array pointers as a first thing to a beginner.
Likewise I wouldn't teach rvalue references as a first thing.

jacobnavia

unread,
Apr 20, 2021, 1:44:25 PM4/20/21
to
Le 20/04/2021 à 18:41, Juha Nieminen a écrit :
> The problem with C is that anything that requires manual construction and
> destruction will "contaminate" (can't think of a better word) anything that
> uses that first thing, with the same requirements. All the way down.
>
> If you have this kind of "string" object that requires manual construction
> and destruction, and you put it as a member of a struct, suddenly that
> struct "inherits" the same requirement. Use that struct in yet another
> struct and that, too, will "inherit" the same requirement.

No.

Just have a constructotr and never free anything. Then link with Boehm's
GARBAGE COLLECTOR and be done with it!

I have already said this in this thread but people will go on arguing
about how difficult is to keep the accounting of memory pieces right.

And they are right but refuse to see the right tool for theirpurpose:
just use a garbage collector and BE DONE WITH IT.

I use an automatic car, no passing of gears. Recently I spoke with a
taxi driver... "Don't you get bored passing gears all day?"

And he acknowledged: yes, My arm hurts at the end of the day. Maybe you
are right, should get an automatic...

Use an automatic garbage collector and BE DONE WITH IT. And please, do
not start with performance problems, in most string applications the gc
is completely transparent.


Alf P. Steinbach

unread,
Apr 20, 2021, 1:53:16 PM4/20/21
to
On 20.04.2021 18:41, Juha Nieminen wrote:
> In comp.lang.c++ Manfred <non...@add.invalid> wrote:
>> Anything that happens behind the scenes and the compiler "does it for
>> you" does require knowledge by the programmer about something they don't
>> see and yet they must be aware of and know how it works.
>> And yes, they are intended to (and actually do) make life easier for the
>> programmer /after/ they master their mechanics, but a beginner's mistake
>> related with such hidden features is likely to lead to somewhat
>> shady/obscure bugs - i.e. hard for said beginner.
>
> The problem with C is that anything that requires manual construction and
> destruction will "contaminate" (can't think of a better word) anything that
> uses that first thing, with the same requirements. All the way down.
>
> If you have this kind of "string" object that requires manual construction
> and destruction, and you put it as a member of a struct, suddenly that
> struct "inherits" the same requirement. Use that struct in yet another
> struct and that, too, will "inherit" the same requirement.
>
> Moreover, suppose you have a complex hierarchy of structs as members of
> other structs, no dynamic memory allocation in sight so far. Then you need
> to add dynamic memory allocation to one of the structs (that's being used
> as a member of other structs etc) and suddenly all that code breaks. If
> there are millions of lines of code using any of those structs that have
> now "inherited" the need for manual construction and destruction (even though
> they were never designed for that initially), you'll have to go and modify
> those millions of lines to follow suit.

Very good points.

In addition, what C++ provides as built-in language features, has to be
supplied as 3d party library or DIY functionality in C.

That means that in C

* one can't leverage a single knowledge base, but must learn each
support library that's needed for those C++ features, and
* the compiler can't be fine tuned to a particular implementation.



> The complexity can quickly grow exponentially.

Not sure about that though, but that it grows, yes.


> Not so in C++, if you design your structs/classes correctly. Even if some
> struct/class very deep in the dependency tree suddenly starts allocating
> memory dynamically, that doesn't matter. None of the other structs/classes
> that may be using it as their members need to be touched in any way. The
> struct/class automatically takes care of its own memory management. You
> don't need to go through the millions of lines of code to add support.
>
>> On a different perspective, consider e.g. rvalue refs and "universal"
>> refs (in Meyers' terms) - they make a useful feature, but good luck
>> explaining it to said beginner.
>
> I wouldn't teach function and array pointers as a first thing to a beginner.
> Likewise I wouldn't teach rvalue references as a first thing.

The "Accelerated C++" book by Andrew Koenig and Barbara Mooooooooo
showed that the high level approach can really work for C++.

But I think that for these fundamental aspects of programming, if one
desires to teach at a high level of abstraction then some other language
will be better suited. I'm thinking C# (which I known from the "classic"
C# days) or, say, Rust (which I don't know beyond "Hello, world!").

A nice feature of C++ is that it's /possible/ to teach programming in a
mixed way, introducing the low level stuff but using containers, strings
etc. It's sad that C++ still lacks standard 128-bit integers and ditto
currency type; still lacks datetime type; still lacks a practically
usable for beginners version of `std::ranges::views::iota` (I prefer to
define things so one can write e.g. `for( const int i: zero_to( n ) )`;
not to mention that the common /C++ implementations/ for Windows still
lack support for UTF-8, both in console and GUI programs, so that
learners can't even write a program that displays their non-English name
in Windows without using 3rd party libraries or the Windows API.

That's just basic functionality.

Try motivating a leaner by coding up the (recursive) C curve in standard
C++ only. Oh, no graphics. Oh well.

So, as I see it standard C++ in itself is not good for learners, but it
can be good with the right 3rd party libraries. I guess you all know
that I've been hobby-working on libraries to fill the mentioned data
type and console i/o voids, and that I've found that it decidedly is a
non-trivial design task (which may be part of the reason why such
libraries don't already exists). And perhaps the good Mr. Fibble's
Neo-something could help with the graphics and sound aspects.

- Alf

Thiago Adams

unread,
Apr 20, 2021, 2:04:30 PM4/20/21
to
On Tuesday, April 20, 2021 at 1:41:41 PM UTC-3, Juha Nieminen wrote:
> In comp.lang.c++ Manfred <non...@add.invalid> wrote:
> > Anything that happens behind the scenes and the compiler "does it for
> > you" does require knowledge by the programmer about something they don't
> > see and yet they must be aware of and know how it works.
> > And yes, they are intended to (and actually do) make life easier for the
> > programmer /after/ they master their mechanics, but a beginner's mistake
> > related with such hidden features is likely to lead to somewhat
> > shady/obscure bugs - i.e. hard for said beginner.
> The problem with C is that anything that requires manual construction and
> destruction will "contaminate" (can't think of a better word) anything that
> uses that first thing, with the same requirements. All the way down.
> If you have this kind of "string" object that requires manual construction
> and destruction, and you put it as a member of a struct, suddenly that
> struct "inherits" the same requirement. Use that struct in yet another
> struct and that, too, will "inherit" the same requirement.
>
> Moreover, suppose you have a complex hierarchy of structs as members of
> other structs, no dynamic memory allocation in sight so far. Then you need
> to add dynamic memory allocation to one of the structs (that's being used
> as a member of other structs etc) and suddenly all that code breaks. If
> there are millions of lines of code using any of those structs that have
> now "inherited" the need for manual construction and destruction (even though
> they were never designed for that initially), you'll have to go and modify
> those millions of lines to follow suit.


To avoid this contamination for **constructors** in C use an static initialization.

For instance:

struct Person { char* name; }
struct House { struct Person person; }
struct House house = {0}; //this constructor does not change

For destructor it is not so easy... My suggestion for C is to
create destructors and "auto" pointers.

Then this would be the required code if C had this feature.

struct Person { char auto* name; }
struct House { struct Person person; }

struct House house = {0};
destroy(house);


> The complexity can quickly grow exponentially.

Yes. People talk about RAII but this specific code generation
is important.

Thiago Adams

unread,
Apr 20, 2021, 2:04:55 PM4/20/21
to
On Tuesday, April 20, 2021 at 2:44:25 PM UTC-3, jacobnavia wrote:
> Le 20/04/2021 à 18:41, Juha Nieminen a écrit :
> > The problem with C is that anything that requires manual construction and
> > destruction will "contaminate" (can't think of a better word) anything that
> > uses that first thing, with the same requirements. All the way down.
> >
> > If you have this kind of "string" object that requires manual construction
> > and destruction, and you put it as a member of a struct, suddenly that
> > struct "inherits" the same requirement. Use that struct in yet another
> > struct and that, too, will "inherit" the same requirement.
> No.
>
> Just have a constructotr and never free anything. Then link with Boehm's
> GARBAGE COLLECTOR and be done with it!

Then use C# or Go.

jacobnavia

unread,
Apr 20, 2021, 2:23:33 PM4/20/21
to
???? And why not in C?

Why should I abandon C if I use a GC?

Kaz Kylheku

unread,
Apr 20, 2021, 2:30:40 PM4/20/21
to
On 2021-04-20, jacobnavia <ja...@jacob.remcomp.fr> wrote:
> Le 20/04/2021 à 18:41, Juha Nieminen a écrit :
>> The problem with C is that anything that requires manual construction and
>> destruction will "contaminate" (can't think of a better word) anything that
>> uses that first thing, with the same requirements. All the way down.
>>
>> If you have this kind of "string" object that requires manual construction
>> and destruction, and you put it as a member of a struct, suddenly that
>> struct "inherits" the same requirement. Use that struct in yet another
>> struct and that, too, will "inherit" the same requirement.
>
> No.
>
> Just have a constructotr and never free anything. Then link with Boehm's
> GARBAGE COLLECTOR and be done with it!

You are talking about a different nuance for construction.

In high level, dynamic languages, constructing an object refers to
aggregation.

The ancient function CONS in Lisp may hgave been the first one to use
the construction terminology. If we make a CONS which contains two
strings, there is not construction of those strings going on; they are
already constructed:

(cons "abc" "def")

Juha is probably talking about C++ style construction where if we have
struct foo { string s; foo(); }, the foo::foo constructor has to
call the string::string constructor.

Juha's remarks are true when objects are put together using composition
rather than aggregation.

Because the object B is embedded in another A, and must be initialized,
then the type-B-specific initialization logic for that embedded object
has to be invoked to initialize that B-typed portion of A.

From an abstraction point of view, it's really a shit design. But the
motivation is solid: there are certain efficiencies you can gain when
you compose rather than aggregate objects. You save space by not
allocating separate objects, reducing allocator overhead, and by not
allocating a pointer to navigate from one to the other. By not chasing a
pointer, you reduce pipeline stalls due to dependent loads. Low-level,
systems programming languages benefit from being able to compose
objects.

It's not worth trying to shoehorn that paradigm into a high level
language though. Composition of objects works for simple, low-level code
that has to run fast, and requires only a simple language.

If your idea for building a web browser or word processor includes
anything like this:

class Document {
std::string documentName;

//
}

due to the immense benefit of not having to chase an extra pointer
or allocate an extra object, you must be a supreme moron, and you will
have your lunch eaten by someone working in Javascript or Python.

> I have already said this in this thread but people will go on arguing
> about how difficult is to keep the accounting of memory pieces right.

> And they are right but refuse to see the right tool for theirpurpose:
> just use a garbage collector and BE DONE WITH IT.

That should be: use a language with a garbage collector and be done
with it.

Statistically/probabilistically speaking, given that you have a genuine
reason to be using C, then conditional probability is high that garbage
collection may not be a good fit for the problem.

> I use an automatic car, no passing of gears. Recently I spoke with a
> taxi driver... "Don't you get bored passing gears all day?"
>
> And he acknowledged: yes, My arm hurts at the end of the day. Maybe you
> are right, should get an automatic...
>
> Use an automatic garbage collector and BE DONE WITH IT. And please, do
> not start with performance problems, in most string applications the gc
> is completely transparent.

GC has good performance. But not in a small space. GC needs more memory
than manual freeing in order to perform well.

Most of the speed (in terms of raw throughput) improvements in GC
revolve around the theme of allowing garbage to linger around longer, so
that garbage cycles are either less frequent (e.g. tuning a larger
heap), or smaller in scope (generational GC, visiting just a young
subset of the object graph in most GC passes).

Allowing garbage to stick around longer translates to one thing: larger
memory footprints.

Adding GC to a simple, embedded application in which manual memory
management can be done correctly with relative ease will just bloat
its footprint. If the footprint is kept very tight, GC will run often
which could degrade the performance.

In a single-process system with physical memory, you can just
do collection whenever memory is nearly exausted, which is optimal.
The memory is needed for nothing else.

In a VM system that is using all of its free memory for a buffer/file
cache, any extra slack space for GC encroaches on that cache, even if no
other applications are running. Which they usually are. VM footprints
matter.

--
TXR Programming Language: http://nongnu.org/txr
Cygnal: Cygwin Native Application Library: http://kylheku.com/cygnal

Richard Damon

unread,
Apr 20, 2021, 2:46:21 PM4/20/21
to
But that only works for a fungible resource like memory. If you really
need to release the resource right away (like a lock on something) you
really want RAII.

jacobnavia

unread,
Apr 20, 2021, 2:47:42 PM4/20/21
to
Le 20/04/2021 à 20:30, Kaz Kylheku a écrit :
> GC has good performance. But not in a small space. GC needs more memory
> than manual freeing in order to perform well.
>

Note: malloc/free do garbage collection by consolidating free memory
blocks into larger ones. This essential FACT is being overlooked here.

But granted, in small embedded applications with less CPU power and less
RAM GC is not the ideal solution and you do the accounting by hand. But
in those environments, C++ is anyway out of the question!

> Most of the speed (in terms of raw throughput) improvements in GC
> revolve around the theme of allowing garbage to linger around longer, so
> that garbage cycles are either less frequent (e.g. tuning a larger
> heap), or smaller in scope (generational GC, visiting just a young
> subset of the object graph in most GC passes).
>

See above. In small spaces GC is not that good. But we were talking
about a TEXT EDITOR for learning C, so that is probably a PC environment
where GC SHINES!

> Allowing garbage to stick around longer translates to one thing: larger
> memory footprints.
>

Yes. But you can obviate that by calling the GC explicitely from time to
time.

> Adding GC to a simple, embedded application in which manual memory
> management can be done correctly with relative ease will just bloat
> its footprint. If the footprint is kept very tight, GC will run often
> which could degrade the performance.
>

Agreed. As I said above, for simple, embedded applications the GC is not
the best solution.

> In a single-process system with physical memory, you can just
> do collection whenever memory is nearly exausted, which is optimal.
> The memory is needed for nothing else.
>
> In a VM system that is using all of its free memory for a buffer/file
> cache, any extra slack space for GC encroaches on that cache, even if no
> other applications are running. Which they usually are. VM footprints
> matter.

There are MANY other solutions in C. You can do pool allocating, for
instance, allocating all the related strings from a pool that is freed
in a single instruction. ETC.


Thiago Adams

unread,
Apr 20, 2021, 3:22:42 PM4/20/21
to
When a language decides for GC this opens the door for
a huge design simplification. No memory management
no pointers etc...

So, if you "pay" for GC you can have a much more simple
and safe language to use. Everything is inside the same bill.

This is what Go language have done.




Kaz Kylheku

unread,
Apr 20, 2021, 3:37:19 PM4/20/21
to
On 2021-04-20, jacobnavia <ja...@jacob.remcomp.fr> wrote:
If you use GC in C, you should be focusing on building a clean,
well-specified language.

If you use GC in C while working on an application, you will actually
be building an ad-hoc, ill-specified, bug-ridden language inside
that application.

https://en.wikipedia.org/wiki/Greenspun%27s_tenth_rule

Kaz Kylheku

unread,
Apr 20, 2021, 4:18:28 PM4/20/21
to
["Followup-To:" header set to comp.lang.c.]
RAII can be entirely separated from memory allocation. RAII can unlock
mutexes and close handles without invalidating memory.

I implemented a form of RAII in TXR Lisp.

This is the TXR Lisp interactive listener of TXR 256.
Quit with :quit or Ctrl-D on an empty line. Ctrl-X ? for cheatsheet.
If unwanted side effects persist, discontinue imperative programming.
1> (defstruct obj ()
(:init (me) (put-line `@me constructed`))
(:fini (me) (put-line `@me finalized`)))
#<struct-type obj>
2> (with-objects ((x (new obj)))
(put-line "inside with-objects"))
#S(obj) constructed
inside with-objects
#S(obj) finalized
t

The object has not gone away; just its finalizer was prematurely
invoked. When the object becomes garbage, that finalizer will not be
called any more; it is no longer registered for finalization.

If an exception goes off while an object is being initialized,
a similar thing happens:

TXR's no-spray organic production means every bug is carefully removed by hand.
1> (defstruct obj ()
(:init (me)
(put-line `@me constructed ... almost`)
(throw 'error))
(:fini (me) (put-line `@me finalized`)))
#<struct-type obj>
2> (new obj)
#S(obj) constructed ... almost
#S(obj) finalized
** exception args: nil
** during evaluation at expr-1:4 of form (throw 'error)
** run with --backtrace to enable backtraces

Struct objects have multiple finalizers at different levels of the
inheritance hierarchy; these get called in well-defined, documented
orders.

You can invoke an object's finalizers directly at any time with
(call-finalizers obj). Using that, and unwind-protect, you can
build your own scoping macros.

For something like acquiring and releasing a lock, we wouldn't
be constructing an object.

This is the problem in C++: RAII is strictly based on constructing and
destroying something, and always requires an object, and that must be a
class.

TXR Lisp offers a standard macro (based on unwind-protect under the hood)
for managing resources that can be allocated with expressions that
produce a value, and then disposed of by passing that value to another
expression. The value can be anything:

Use TXR only as directed. Unless you are intelligent, curious and creative.
1> (defun make-widget (arg)
(put-line `make-widget: @arg`)
arg)
make-widget
2> (defun break-widget (arg)
(put-line `break-widget: @arg`))
break-widget
3> (with-resources ((x (make-widget 42) (break-widget x))
(y (make-widget 'foo) (break-widget y)))
(put-line "inside with-resources"))
make-widget: 42
make-widget: foo
inside with-resources
break-widget: foo
break-widget: 42

Chris M. Thomasson

unread,
Apr 20, 2021, 4:19:45 PM4/20/21
to
On 4/20/2021 10:44 AM, jacobnavia wrote:
> Le 20/04/2021 à 18:41, Juha Nieminen a écrit :
>> The problem with C is that anything that requires manual construction and
>> destruction will "contaminate" (can't think of a better word) anything
>> that
>> uses that first thing, with the same requirements. All the way down.
>>
>> If you have this kind of "string" object that requires manual
>> construction
>> and destruction, and you put it as a member of a struct, suddenly that
>> struct "inherits" the same requirement. Use that struct in yet another
>> struct and that, too, will "inherit" the same requirement.
>
> No.
>
> Just have a constructotr and never free anything. Then link with Boehm's
> GARBAGE COLLECTOR and be done with it!

Wow! Really? Did you know that manual memory management can actually
help a GC'ed environment run more efficiently? I have had to deal with
questions about performance issues wrt using a garbage collector to act
as a memory reclamation scheme for lock-free algorithms for almost two
decades. GC can become a rather nasty, unpredictable, bottleneck.

Kaz Kylheku

unread,
Apr 20, 2021, 4:29:41 PM4/20/21
to
On 2021-04-20, Chris M. Thomasson <chris.m.t...@gmail.com> wrote:
> On 4/20/2021 10:44 AM, jacobnavia wrote:
>> Le 20/04/2021 à 18:41, Juha Nieminen a écrit :
>>> The problem with C is that anything that requires manual construction and
>>> destruction will "contaminate" (can't think of a better word) anything
>>> that
>>> uses that first thing, with the same requirements. All the way down.
>>>
>>> If you have this kind of "string" object that requires manual
>>> construction
>>> and destruction, and you put it as a member of a struct, suddenly that
>>> struct "inherits" the same requirement. Use that struct in yet another
>>> struct and that, too, will "inherit" the same requirement.
>>
>> No.
>>
>> Just have a constructotr and never free anything. Then link with Boehm's
>> GARBAGE COLLECTOR and be done with it!
>
> Wow! Really? Did you know that manual memory management can actually
> help a GC'ed environment run more efficiently? I have had to deal with
> questions about performance issues wrt using a garbage collector to act
> as a memory reclamation scheme for lock-free algorithms for almost two
> decades. GC can become a rather nasty, unpredictable, bottleneck.

Snip of a recent commit of mine:

commit 174182a29c785493ed41231caff36f35c56819cf
Author: Kaz Kylheku <k...@kylheku.com>
Date: Tue Apr 6 20:06:18 2021 -0700

gc: fix astonishing bug in weak hash processing.

This is a flaw that has been in the code since the initial
implementation in 2009.

:)

Chris M. Thomasson

unread,
Apr 20, 2021, 4:29:48 PM4/20/21
to
On 4/20/2021 11:47 AM, jacobnavia wrote:
> Le 20/04/2021 à 20:30, Kaz Kylheku a écrit :
>> GC has good performance. But not in a small space. GC needs more memory
>> than manual freeing in order to perform well.
>>
>
> Note: malloc/free do garbage collection by consolidating free memory
> blocks into larger ones. This essential FACT is being overlooked here.

consolidating smaller blocks into larger ones has nothing to do with the
conventional notion of a garbage collector. So, you saying that
malloc/free do GC is very odd to me, indeed.

Chris M. Thomasson

unread,
Apr 20, 2021, 4:39:31 PM4/20/21
to
Wow! I remember somebody asking me why the GC is under so much pressure.
They showed me their code where most of the allocations were coming
from. Of course they were allocating a new node for every push into a
lock-free stack! I said the simplest thing I could think of, just to see
if it made a difference... It was: Create a LIFO node cache, and create
a layered approach for allocating nodes. Try the cache first, then
resort to allocating a new node for now. The response was well, then we
have to use manual memory management for the node cache. I said, indeed
you do! So, they did it. Guess what? They were able to increase
performance by orders of magnitude! The GC had a lot less pressure on
it. Iirc, this was in Java. All due to good ol' manual memory management
even in a GC system.

Ian Collins

unread,
Apr 20, 2021, 4:56:41 PM4/20/21
to
On 21/04/2021 05:44, jacobnavia wrote:
> Le 20/04/2021 à 18:41, Juha Nieminen a écrit :
>> The problem with C is that anything that requires manual construction and
>> destruction will "contaminate" (can't think of a better word) anything that
>> uses that first thing, with the same requirements. All the way down.
>>
>> If you have this kind of "string" object that requires manual construction
>> and destruction, and you put it as a member of a struct, suddenly that
>> struct "inherits" the same requirement. Use that struct in yet another
>> struct and that, too, will "inherit" the same requirement.
>
> No.
>
> Just have a constructotr and never free anything. Then link with Boehm's
> GARBAGE COLLECTOR and be done with it!

Let's not forget that memory isn't the only resource that a program has
to manage.

--
Ian.

Chris M. Thomasson

unread,
Apr 20, 2021, 5:07:23 PM4/20/21
to
On 4/20/2021 10:44 AM, jacobnavia wrote:
For some reason, anytime I hear somebody go, just use a GC... Well, it
makes think of this song:

https://youtu.be/WJabPYimznY

Or the following scene in Disney's Dragonslayer from 1981:

https://youtu.be/TJRaLnLDMWg

Chris M. Thomasson

unread,
Apr 20, 2021, 5:29:48 PM4/20/21
to
For the life of me I cannot remember if this was using Java or C with
the Bohem collector.

James Kuyper

unread,
Apr 20, 2021, 7:13:18 PM4/20/21
to
My point is that he was responding to a claim that it couldn't be done,
not a claim that it was inconveniently difficult to do. For the claim he
was actually responding to, his response was entirely accurate.

That there were platforms where it couldn't be done was given as a
reason why the standard doesn't require that it be done. The real reason
is that there are platforms where it's inconveniently difficult to do.

Juha Nieminen

unread,
Apr 21, 2021, 12:37:29 AM4/21/21
to
In comp.lang.c++ jacobnavia <ja...@jacob.remcomp.fr> wrote:
> See above. In small spaces GC is not that good. But we were talking
> about a TEXT EDITOR for learning C, so that is probably a PC environment
> where GC SHINES!

If that's the case, then just use a language like Java or Python.

Or, you know, C++.

At least you won't be needing to use non-standard third-party extensions.

Chris M. Thomasson

unread,
Apr 21, 2021, 6:27:13 PM4/21/21
to
Fwiw, using an "extension" in C is nothing new. Want to create a server,
better learn how to use sockets. If you want to go deep, create your own
TCP stack. Is creating a driver in C to use a 3d party device "cheating"
by using third-party extensions? third-party API's?

Luckily for me a lot of my recent work is 100% workable with pure C and
graphics. Pure C generating a PPM image is absolutely perfect. 100%
portable and no 3d party code used in any way, shape or form. :^)

If the user wants to view the result of my C code, they can choose any
PPM viewer they want to. There, pure C code generating fractals, vector
fields, abstract art, ect. ;^)

Way back in the day I would be forced to use "extensions" to C. Things
like POSIX, Pthreads, my own externally assembled atomic's libs for
various archs using MASM and GAS, ect... I still have a bunch of code
that uses the good ol' Pthreads for win32 lib here:

https://sourceware.org/pthreads-win32

When C/C++ 11 made atomic's and threading standard, it was really nice,
but made me want to port all of my older code from Pthreads and assembly
language to the new stuff.

Here is a result of some of my C code generating a single file for
another program, PovRay, to raytrace for me:

https://youtu.be/skGUAXAx6eg

https://youtu.be/R0v_1EBAOr4

Chris M. Thomasson

unread,
Apr 22, 2021, 3:26:57 AM4/22/21
to
On 4/19/2021 9:42 PM, Chris M. Thomasson wrote:
> On 4/17/2021 10:12 AM, Manfred wrote:
>> (cross-posting to comp.lang.c to let the heat fire up)
>>
>> On 4/17/2021 7:56 AM, Juha Nieminen wrote:
>>> I know that this is probably going to piss off avid C programmers,
>>> but I don't really care all that much.
>>
>> Excellent start
>>
>> [...]
>>>
>>> I myself happen to know both languages quite well, and in fact I
>>> program in both languages as my profession, and I would say that
>>> I am very knowledgeable and proficient in both languages and have
>>> no trouble in developing in either one. I am of the strong opinion
>>> that no, C is not a "simple" language, and that C++ makes so many
>>> things so much simpler, easier and safer.
>>>
>>> I was reminded of this in a quite concrete manner recently when I was
>>> trying to help a beginner programmer who had decided to try his hands
>>> on C, and wanted to create a simple text editor, which would read a
>>> text file, allow the user to edit the text in several ways, and to
>>> save the file.
>>>
>>> It quite quickly turned very frustrating for *both* of us ...
>>
>> The first comment that comes to mind is that probably part of the
>> frustration stems from your approach (no offense) to try and use in C
>> a design that would be suitable for C++ instead. Despite their name,
>> the two are quite different languages. The similarities are about
>> their runtime footprint, not about how they are used.
>>
>> For example, at a first glance the design of string object is not that
>> great for C.
>> A common pattern that pops to the mind is to use opaque structs (see
>> e.g. FILE*), and have something like:
>>
>> typedef struct string_t STRING;
>>
>> STRING* alloc_string(); // creates the empty string
>> STRING* set_string(STRING* s, const char* value);
>> void free_string(STRING* s);
>> STRING* concat_string(STRING* s1, STRING* s2);
>> ...
>>
>> This way there would be no ambiguity about how the STRING object has
>> to be used.
>> And yes, in C you have to do all yourself - C is a low level language,
>> and it is meant for low level stuff. It is hardly the right tool for
>> anything meant to interface with a human user - it is (still) great
>> for system programming and back-end processing, IMO.
>>
>> As second, I agree with Jacob that a text editor is a simple tool, but
>> it is not easy to make expecially for a beginner.
>>
>>
>> [snip]
>
> I would not call C easy. Instead, I would refer to it as being more to
> "the point", perhaps? Things have to be "explicit", it can be more
> verbose. I say that because C does not have RAII. Also, C is really nice
> to create plugins wrt creating portable bindings to other languages.
> Usually in C, things tend go like the following when coding up a new
> "object", so to speak:
>
> Typing in the newsreader, sorry for typos.
> ________________________________
>
> #include <stdio.h>
> #include <stdlib.h>
>
>
> struct foo
> {
>    int a;
>    void* mem;
> };
>
> int foo_create(struct foo* const self)
> {
>    if ((self->mem = malloc(sizeof(42))))
>    {
>        self->a = 84;
>        printf("foo_create(%p)\n", (void*)self);
>        return 1;
>    }
>
>    return 0;
> }
>
> void foo_destroy(struct foo const* const self)
> {
>    printf("foo_destroy(%p)\n", (void*)self);
>    free(self->mem);
> }
>
> void foo_output(struct foo const* self, FILE* output)
> {
>    printf("foo_output(%p)\n", (void*)self);
>    fprintf(output, "struct foo(%p)->a = %d\n", (void*)self, self->a);
>    fprintf(output, "struct foo(%p)->mem = %p\n", (void*)self, self->mem);
> }
>
>
> int main()
> {
>    struct foo foo;
>
>    if (foo_create(&foo))
>    {
>        foo_output(&foo, stdout);
>
>        foo_destroy(&foo);
>    }
>
>    return 0;
> }
> ________________________________
>
>
> See how I have to make an explicit call to foo_destroy? This can be
> handled in C++ in a "cleaner/safer" manner. Again, typing in the
> newsreader sorry for typos. This would be a simple C++ wrapper to the C
> API above:
> ________________________________
>
> struct foo_wrapper
> {
>    struct foo m_foo;
>
>    foo_wrapper() { if (! foo_create(&m_foo)) throw; }
>    ~foo_wrapper() { foo_destroy(&m_foo); }
>
>    void output(FILE* output_) const
>    {
>        foo_output(&m_foo, output_);
>    }
> };
>
> ________________________________
>
> Then we can go:
>
> {
>    foo_wrapper foo;
>    foo.output(stdout);
> }
>
> There. If it fails to create a foo in foo_create, it throws. Otherwise,
> foo_destroy is guaranteed to be called when foo goes out of scope. So,
> C++ can be more convenient, and safer. No need to use all of C++, but it
> does have its moments.

First learning about RAII, even when I learned about ScopeGuard, back in
my C++ days, for some reason, makes me think of the following song:

https://youtu.be/UZ2-FfXZlAU

Even though I was working with C/C++ well before this song was created,
it makes think way back when I was a little kid using basic on my Atari.

Malcolm McLean

unread,
Apr 22, 2021, 8:59:31 AM4/22/21
to
On Wednesday, 21 April 2021 at 23:27:13 UTC+1, Chris M. Thomasson wrote:
> On 4/20/2021 9:37 PM, Juha Nieminen wrote:
> > In comp.lang.c++ jacobnavia <ja...@jacob.remcomp.fr> wrote:
> >> See above. In small spaces GC is not that good. But we were talking
> >> about a TEXT EDITOR for learning C, so that is probably a PC environment
> >> where GC SHINES!
> >
> > If that's the case, then just use a language like Java or Python.
> >
> > Or, you know, C++.
> >
> > At least you won't be needing to use non-standard third-party extensions.
> >
> Fwiw, using an "extension" in C is nothing new. Want to create a server,
> better learn how to use sockets. If you want to go deep, create your own
> TCP stack. Is creating a driver in C to use a 3d party device "cheating"
> by using third-party extensions? third-party API's?
>
> Luckily for me a lot of my recent work is 100% workable with pure C and
> graphics. Pure C generating a PPM image is absolutely perfect. 100%
> portable and no 3d party code used in any way, shape or form. :^)
>
> If the user wants to view the result of my C code, they can choose any
> PPM viewer they want to. There, pure C code generating fractals, vector
> fields, abstract art, ect. ;^)
>
There are plenty of image format codecs in my Baby X resource compiler.

It's on github.
https://github.com/MalcolmMcLean/babyxrc

Chris M. Thomasson

unread,
Apr 22, 2021, 2:58:38 PM4/22/21
to
Thanks for the heads up Malcolm! :^)

For purely portable, for some reason, I tend toward PPM, perhaps because
its so easy. If I need a png or something I have code that uses Cairo,
which is a pretty nice C library.

jacobnavia

unread,
Apr 23, 2021, 6:10:48 AM4/23/21
to
Le 20/04/2021 à 23:07, Chris M. Thomasson a écrit :

>
> For some reason, anytime I hear somebody go, just use a GC... Well, it
> makes think of this song:
>
> https://youtu.be/WJabPYimznY
>
> Or the following scene in Disney's Dragonslayer from 1981:
>
> https://youtu.be/TJRaLnLDMWg

Great arguments!

Technically sound, I am convinced now...


... that you have nothing to say

Malcolm McLean

unread,
Apr 23, 2021, 10:23:24 AM4/23/21
to
Generally I use PNGs as a default image format. They have 24 bit plus alpha, so
they can be used for anything except really high end work where you need more
than 8 bits per channel. And they're lossless and reasonably well-compressed.

Chris M. Thomasson

unread,
Apr 23, 2021, 3:37:54 PM4/23/21
to
Have you ever had to deal with GC issues? I have... Unfortunately, some
people seem to think that a full blown GC is okay to use for reclaiming
memory in lock-free algorithms. It works, but can become a massive,
nasty, unpredictable bottleneck. Only then do they complain. I warned
some of them...

https://youtu.be/XcxKIJTb3Hg

Chris M. Thomasson

unread,
Apr 23, 2021, 3:38:39 PM4/23/21
to
The bunny is the GC when you expose your lock-free system to high levels
of sustained load.

Chris M. Thomasson

unread,
Apr 23, 2021, 3:43:54 PM4/23/21
to
On 4/23/2021 12:37 PM, Chris M. Thomasson wrote:
I used to cringe when I would see code that would allocate nodes all
over the place, and never free them. Sometimes they would allocate nodes
in loops! They would say, ahhh, that's for the GC. WOW!

jacobnavia

unread,
Apr 23, 2021, 4:38:26 PM4/23/21
to
Thanks for your reply, at least there are some real arguments and not
that stupid links.

As You seem to know, the GC can be misused, as anything can be misused.
The GC is great in certain situations, not in others.

I used it in my debugger. It was a GREAT relief to be able to allocate a
piece of memory in the user interface, pass it to the debugger without
caring about when and by whom it should be freed. Obviously the memory
pieces being passed were user inputs,names of variables to be
displayed,and many other SHORT pieces of memory. The other solution
would bve to redesign the debugger and use a thread proof memory
allocator that would keep the accounting,or other massive development
projects.

By using the GC I was able to concentrate ion the debugger itself,
already a very complex program, and forget about the memory accounting
chors.

What is nice of the GC is the SIMPLIFICATION that provides. Of course it
can be abused, as everything.

Knives? Wondefful for cutting bread. But they can cut your fingers too.

USE WITH CARE!

GC is no differeent

Chris M. Thomasson

unread,
Apr 23, 2021, 4:44:50 PM4/23/21
to
Okay, that is fair enough. From my experience, GC it not okay to use in
high pressure scenarios, under lots of load. It can cause all sorts of
issues.


> I used it in my debugger. It was a GREAT relief to be able to allocate a
> piece of memory in the user interface, pass it to the debugger without
> caring about when and by whom it should be freed. Obviously the memory
> pieces being passed were user inputs,names of variables to be
> displayed,and many other SHORT pieces of memory. The other solution
> would bve to redesign the debugger and use a thread proof memory
> allocator that would keep the accounting,or other massive development
> projects.
>
> By using the GC I was able to concentrate ion the debugger itself,
> already a very complex program, and forget about the memory accounting
> chors.
>
> What is nice of the GC is the SIMPLIFICATION that provides. Of course it
> can be abused, as everything.
>
> Knives? Wondefful for cutting bread. But they can cut your fingers too.
>
> USE WITH CARE!
>
> GC is no differeent

Just, try not to use a GC as a crutch: The way of all things. Btw, some
people say C is too dangerous to use, GC or not... Its as if they want
people to be forced to put corks on their forks to prevent themselves
from hurting themselves.

https://youtu.be/SKDX-qJaJ08

lol.

Ian Collins

unread,
Apr 23, 2021, 6:56:42 PM4/23/21
to
On 24/04/2021 08:38, jacobnavia wrote:
>
> By using the GC I was able to concentrate ion the debugger itself,
> already a very complex program, and forget about the memory accounting
> chors.
>
> What is nice of the GC is the SIMPLIFICATION that provides. Of course it
> can be abused, as everything.

We could use exactly the same argument for RAII and in the context of
this thread, std::string. We could also add deterministic behaviour as
a significant advantage RAII has over GC, especially in the semi-real
time embedded world where much of today's C and C++ is written.


> Knives? Wondefful for cutting bread. But they can cut your fingers too.
>
> USE WITH CARE!
>
> GC is no differeent


Very true.


GC can be a survivor when working with old, leaky, C code. I have
deployed it with long running services that would run out of memory and
die. It not only kept the thing running, but that particular library
kept logs which could be used to find and fix the worst leaks. I have
yet to use abetter tool for this job.

--
Ian.


Chris M. Thomasson

unread,
Apr 23, 2021, 7:09:18 PM4/23/21
to
Yeah, I don't think I ever used GC for that, but it sure should help out
indeed. Good one! :^)

I while back, have had to find a find a horrible race-condition that
would sometimes miss a call to free. So, sometimes, under certain
conditions, the memory could grow out of control. It was a bitch and a
half to debug. Iirc, it was a strange reference counting error that
would not allow some things to be destroyed, so they would just hang
around...

I also remember debugging something that would hang up, from time to
time. Iirc, the problem was that a mutex was not being unlocked under
certain conditions. This was easier to fix than the previous one. The
system would deadlock, and then I could go in and debug it in an actual
deadlocked state.

Kaz Kylheku

unread,
Apr 23, 2021, 11:51:48 PM4/23/21
to
["Followup-To:" header set to comp.lang.c.]
Allocating a node in the loop such that it is unreachable is actually a
pretty easy situation for GC.

(while true
(cons x y))

The cons cell is allocated and immediately becomes unreachable. Being
unreachable it means that when GC is triggered, it does not have to
traverse it.

Traversing the graph of reachable objects is a big expense.

It will have to sweep these cons cells to reclaim them. But that would
have to be done by explicit memory management also!

Sweeping can be a big expense in a naive mark-and-sweep allocator.
A generational garbage collector, though, makes the sweep cheaper.

A generational garbage collector will allocate these new cells into
a "nursery" of baby objects.

When a certain limit is reached, a generational GC pass is triggered.
The GC walks the object graph, but it avoids traversing across "mature"
objects; it looks at areas like the machine registers and stack, looking
only for baby objects. It will not find any of these baby objects
because only the loop is creating them, and it loses track of each one.

Then comes the generational sweep phase. Unlike in a full GC pass,
the garbage collector only has to sweep through the nursery. The nursery
is chock full of these cons cells made in the body of the loop. None
of them were marked reachable, so they are deallocated.

This sweep pass does not have to look at any mature objects at all.

Basically, GC can be designed and tuned to deal with the pressure of a
fast allocation of temporary objects that soon become garbage.
Such cases arise in practical applications. For instance, macro
expansion in Lisp. Or string processing. Or calculations with numbers
that have to be boxed on the heap, like bignums and sometimes
floating-point values.

Chris M. Thomasson

unread,
Apr 24, 2021, 3:48:22 PM4/24/21
to
Ahhh yes. But I failed to give more context Kaz, sorry! Basically, the
loop would receive events from observer threads, and create responses
via allocating new nodes and pushing them into lock-free queues. The
problem was that the frequency of this event loop would get "hot", and
end up creating a shi% load of new nodes, not a free in sight. The GC
would get radically loaded up.

Chris M. Thomasson

unread,
Apr 24, 2021, 5:26:45 PM4/24/21
to
Fwiw, here is the result of some older C code generate each frame for
the following animation:

https://youtu.be/TLd64a4gdZQ

Chris M. Thomasson

unread,
Apr 24, 2021, 5:29:06 PM4/24/21
to
This one is an example of a special way I handle the result of a
distance estimator for the Mandelbulb:

https://youtu.be/XpbPzrSXOgk

Also, a friend of mine put some of my work on his fractal page:

http://paulbourke.net/fractals/multijulia

Robert Latest

unread,
Apr 26, 2021, 5:02:16 AM4/26/21
to
["Followup-To:" header set to comp.lang.c.]
Juha Nieminen wrote:
> The problem with C is that anything that requires manual construction and
> destruction will "contaminate" (can't think of a better word) anything that
> uses that first thing, with the same requirements. All the way down.

Of course it does. If you insist on doing it in C, it's best to define
functions new_xxx and free_xxx for each type xxx, and make sure that free_yyy
is called from free_xxx if the xxx type contains dynamically allocated objects
of type yyy. It's really not hard, just requires a bit of typing, but it's a
tremendously helpful exercise especially for a beginner.

BTW if this concept is new or "The problem" for you, I don't believe you have
much experience with C.

> If you have this kind of "string" object that requires manual construction
> and destruction, and you put it as a member of a struct, suddenly that
> struct "inherits" the same requirement. Use that struct in yet another
> struct and that, too, will "inherit" the same requirement.


Trivially dealt with, see above.

> Moreover, suppose you have a complex hierarchy of structs as members of
> other structs, no dynamic memory allocation in sight so far. Then you need
> to add dynamic memory allocation to one of the structs (that's being used
> as a member of other structs etc) and suddenly all that code breaks.

...as it would in any language if you change the architecture of your design
without making the appropriate changes where needed.

> If
> there are millions of lines of code using any of those structs that have
> now "inherited" the need for manual construction and destruction (even though
> they were never designed for that initially), you'll have to go and modify
> those millions of lines to follow suit.

If that's a problem for you, just don't use a strongly typed language.

> Not so in C++, if you design your structs/classes correctly.

Your argument above is based on NOT designing your data structures correctly in
C, and now you say C++ handles better but only IF you do it correctly? Doesn't
make sense.

Even when trying to do everything explicitly right in C you will make mistakes.
Or I will, anyway. I've found valgrind to be an invaluable tool when debugging
C code.

I'm very happy with teaming up C and Python. Low level ultrafast stuff in C
extension, high level OO garbage-collected stuff in Python. In the 90's I did a
largish project in C++ and never grew to like it much.

--
robert


Robert Latest

unread,
Apr 26, 2021, 5:25:10 AM4/26/21
to
["Followup-To:" header set to comp.lang.c.]
jacobnavia wrote:
> Just have a constructotr and never free anything. Then link with Boehm's
> GARBAGE COLLECTOR and be done with it!

Whan I find myself in a situation where explicitly freeing resources becomes
unwieldy I start thinking about using another language than C (which, like
i.e. Python, comes with garage collection).

If you're too lazy to use free(), you should also be too lazy to use malloc()
or to declare your variables. Better to use a language where you don't have to
do any of it.

--
robert

Robert Latest

unread,
Apr 26, 2021, 5:28:08 AM4/26/21
to
["Followup-To:" header set to comp.lang.c.]
jacobnavia wrote:
> I use an automatic car, no passing of gears. Recently I spoke with a
> taxi driver... "Don't you get bored passing gears all day?"
>
> And he acknowledged: yes, My arm hurts at the end of the day. Maybe you
> are right, should get an automatic...

The C-with-GC analogy would be to first buy a stick shift car and then
expensively retrofit it with an automatic transmiassion (probably voiding the
warranty in the process). Better to buy a car that already comes with automatic
transmission.

--
robert

Robert Latest

unread,
Apr 26, 2021, 5:30:22 AM4/26/21
to
Ian Collins wrote:
> GC can be a survivor when working with old, leaky, C code. I have
> deployed it with long running services that would run out of memory and
> die. It not only kept the thing running, but that particular library
> kept logs which could be used to find and fix the worst leaks. I have
> yet to use abetter tool for this job.

Valgrind?

--
robert

Juha Nieminen

unread,
Apr 26, 2021, 5:43:14 AM4/26/21
to
In comp.lang.c++ Robert Latest <bobl...@yahoo.com> wrote:
> Your argument above is based on NOT designing your data structures correctly in
> C, and now you say C++ handles better but only IF you do it correctly? Doesn't
> make sense.

Are you telling be that every single time you create a struct in a C program
you always write a "constructor" and "destructor" for it, just in case that
some time in the future they might start requiring them?

Well, even if you actually do, my point stands: In C++ you don't need to do
that.

Malcolm McLean

unread,
Apr 26, 2021, 6:08:57 AM4/26/21
to
You operate at a sligly higher level than that. A "struct point" wouldn't have a
constructor / destructor pair.
But if a structure is either "open" enough that it might be extended to contain
other embedded pointers, e.g. a "struct employee" with char *name field
might well acquire other string fields as the program develops, or if it is
sufficiently complicated to justify opaque operations, then it would routinely
have a constructor / destructor pair.

In C++, as long as you use stl, destruction is done for you, and you can also
move between putting objects on the heap or on the stack smoothly.
C++ is better in this regard. However it's not usually a huge deal - the pattern
is that you create the object on the heap, you pass it about as an opaque
pointer and manipulate it with functions defined in its object defintion file,
then when you are done with it you call the destructor. It's all routine
programming.

Robert Latest

unread,
Apr 26, 2021, 6:35:11 AM4/26/21
to
["Followup-To:" header set to comp.lang.c.]
Juha Nieminen wrote:
> In comp.lang.c++ Robert Latest <bobl...@yahoo.com> wrote:
>> Your argument above is based on NOT designing your data structures correctly
>> in C, and now you say C++ handles better but only IF you do it correctly?
>> Doesn't make sense.
>
> Are you telling be that every single time you create a struct in a C program
> you always write a "constructor" and "destructor" for it,

Of course not.

> just in case that some time in the future they might start requiring them?

No. Only if the data structure requires it, as is typical with nested,
dynamically allocated structs. Then of course it requires only a few extra
lines of code per struct type. If your struct is used for any non-trivial
amount of work you'll need some init code anyway.

> Well, even if you actually do, my point stands: In C++ you don't need to do
> that.

Then why don't you use C++ if thinking about memory management bothers you so
much?

--
robert

Juha Nieminen

unread,
Apr 26, 2021, 9:08:12 AM4/26/21
to
In comp.lang.c++ Robert Latest <bobl...@yahoo.com> wrote:
> ["Followup-To:" header set to comp.lang.c.]
> Juha Nieminen wrote:
>> In comp.lang.c++ Robert Latest <bobl...@yahoo.com> wrote:
>>> Your argument above is based on NOT designing your data structures correctly
>>> in C, and now you say C++ handles better but only IF you do it correctly?
>>> Doesn't make sense.
>>
>> Are you telling be that every single time you create a struct in a C program
>> you always write a "constructor" and "destructor" for it,
>
> Of course not.

That's exactly my point:

Suppose you have a huge amount of code, with lots and lots of structs, structs
as members of other structs, and a lot of code that uses those structs.

Then one day you decide to add a dynamically allocated array (such as a
dynamically allocated string) to one of the structs, which is used in
lots of other structs, which themselves are used in other structs, and
so on...

Because of a chance in this one struct, suddenly lots of other structs
"inherit" its need for manual construction and destruction, and tons of
code that uses those other structs need to be refactored to now call the
constructors and destructors of all the structs that now require it.
All because of this one struct.

In C++, you don't have to. You just add the dynamic data structure to
that one struct, and you're done. No need to modify anything else.

>> Well, even if you actually do, my point stands: In C++ you don't need to do
>> that.

> Then why don't you use C++ if thinking about memory management bothers you so
> much?

That's exactly what I do, whenever I can.

Richard Damon

unread,
Apr 26, 2021, 9:31:03 AM4/26/21
to
Yes, in C if you make that sort of change, you need to change a lot of code.

That is also, if you really think about it, a major breaking change in
the definition of the struct.

BECAUSE in C, you need to manual handle allocation, this is a breaking
API change to a structure from DEFINED to not need management, to
needing management. In C, you need to think ahead and ask if you can
conceive of a need to manage the life of the structure, and if so add
explicit create/destroy methods, even if for now, they do nothing. (You
can make them in-liene no-ops so can be no cost.

Yes, that is one of the advantages of C++, that you can easily change
this as C++ will automatically make this change for you. It also can be
seen as a weakness. It can be easy to make an apparently innocent
looking change that affects the performance of wide swathes of code as
that code get replicated everywhere.

Malcolm McLean

unread,
Apr 26, 2021, 9:55:43 AM4/26/21
to
On Monday, 26 April 2021 at 14:08:12 UTC+1, Juha Nieminen wrote:
> In comp.lang.c++ Robert Latest <bobl...@yahoo.com> wrote:
> > ["Followup-To:" header set to comp.lang.c.]
> > Juha Nieminen wrote:
> >> In comp.lang.c++ Robert Latest <bobl...@yahoo.com> wrote:
> >>> Your argument above is based on NOT designing your data structures correctly
> >>> in C, and now you say C++ handles better but only IF you do it correctly?
> >>> Doesn't make sense.
> >>
> >> Are you telling be that every single time you create a struct in a C program
> >> you always write a "constructor" and "destructor" for it,
> >
> > Of course not.
> That's exactly my point:
>
> Suppose you have a huge amount of code, with lots and lots of structs, structs
> as members of other structs, and a lot of code that uses those structs.
>
> Then one day you decide to add a dynamically allocated array (such as a
> dynamically allocated string) to one of the structs, which is used in
> lots of other structs, which themselves are used in other structs, and
> so on...
>
> Because of a chance in this one struct, suddenly lots of other structs
> "inherit" its need for manual construction and destruction, and tons of
> code that uses those other structs need to be refactored to now call the
> constructors and destructors of all the structs that now require it.
> All because of this one struct.
>
This can happen. One case is where a string is changed from a fixed-width field
to a char *.
But a catastropic propagation of the change is unlikely. That's because most
structs are either inherently unlikely to change during the lifetime of the project,
or they are only used in a few places in relatively shallow hierachies. Or they
are aleady encapsulated with destructors.

Robert Latest

unread,
Apr 26, 2021, 10:15:30 AM4/26/21
to
["Followup-To:" header set to comp.lang.c.]
Juha Nieminen wrote:
>
> Suppose you have a huge amount of code, with lots and lots of structs,
> structs as members of other structs, and a lot of code that uses those
> structs.

In other words, a mess.

> Then one day you decide to add a dynamically allocated array (such as a
> dynamically allocated string) to one of the structs, which is used in lots of
> other structs, which themselves are used in other structs, and so on...

I get your point. BTW I take "lots of structs" to mean "lots of struct types,"
not "lots of instances"

> Because of a chance in this one struct, suddenly lots of other structs
> "inherit" its need for manual construction and destruction, and tons of code
> that uses those other structs need to be refactored to now call the
> constructors and destructors of all the structs that now require it. All
> because of this one struct.

Correct. At fault is not the language, nor the lack of a garbage collector, but
an ill-designed and unmaintainable pile of code which is badly in need of
refactoring anyway. Are you speaking of a real-life example? Dozens of
different struct types that all include one another and none of which
(originally) have dynamically allocated members?

--
robert

Robert Latest

unread,
Apr 26, 2021, 10:22:28 AM4/26/21
to
Richard Damon wrote:
> That is also, if you really think about it, a major breaking change in
> the definition of the struct.

Absolutely. This is why if you write a library to be used by someone else, and
which uses a struct type in its API, you should provide creation and
destruction functions for that struct. As long as your struct is simple, this
is trivial. When it grows in complexity, you are thankful for your foresight.

--
robert

Scott Lurndal

unread,
Apr 26, 2021, 2:57:30 PM4/26/21
to
Add padding to the struct, and if necessary a version field. As functionality
is added ot the struct over time, the version field indicates what level
of functionality is provided and the padding allows future expansion without breaking
existing linked applications.

jacobnavia

unread,
Apr 26, 2021, 3:24:27 PM4/26/21
to
I used the GC extensively in my debugger and in the IDE for lcc-win.
The IDE must receive user inputs and pass them to the debugger, the
debugger must receive them and then use them during a certain time, for
instance when displaying info for the current line. When the user gose
to the next line, the debugger should free all the allocations done for
that line and prepare the display for the one that the IDE says the
program stopped.

This back and forth can be solved with several heaps, a heap for each
line, etc. That would solve that problem but the rest of the problems
would need also other allocations and freeing, etc.

The debugger and the IDE run in different threads, and the user program
runs in yet another thread. Look, of course I am a lazy guy, I am alone
developing all that, and , due to my laziness, I like solutions

THAT WORK WITHOUT TOO MUCH EFFORT!

And no, I will not change the language to write a low level debugger in
python (imagine that)

Juha Nieminen

unread,
Apr 27, 2021, 12:38:03 AM4/27/21
to
In comp.lang.c++ Robert Latest <bobl...@yahoo.com> wrote:
>> Because of a chance in this one struct, suddenly lots of other structs
>> "inherit" its need for manual construction and destruction, and tons of code
>> that uses those other structs need to be refactored to now call the
>> constructors and destructors of all the structs that now require it. All
>> because of this one struct.
>
> Correct. At fault is not the language, nor the lack of a garbage collector, but
> an ill-designed and unmaintainable pile of code which is badly in need of
> refactoring anyway. Are you speaking of a real-life example? Dozens of
> different struct types that all include one another and none of which
> (originally) have dynamically allocated members?

But you yourself said that you don't automatically add manual constructors
and destructors to every struct you write, just in case.

I don't even understand what you mean by "well-designed code" then.
Is "well-designed" C code one where every struct has a constructor and
destructor function (that you always call for every single instance)?
Or is it something else?

Robert Latest

unread,
Apr 27, 2021, 3:22:50 AM4/27/21
to
["Followup-To:" header set to comp.lang.c.]
Juha Nieminen wrote:
> In comp.lang.c++ Robert Latest <bobl...@yahoo.com> wrote:
>>> Because of a chance in this one struct, suddenly lots of other structs
>>> "inherit" its need for manual construction and destruction, and tons of
>>> code that uses those other structs need to be refactored to now call the
>>> constructors and destructors of all the structs that now require it. All
>>> because of this one struct.
>>
>> Correct. At fault is not the language, nor the lack of a garbage collector,
>> but an ill-designed and unmaintainable pile of code which is badly in need
>> of refactoring anyway. Are you speaking of a real-life example? Dozens of
>> different struct types that all include one another and none of which
>> (originally) have dynamically allocated members?
>
> But you yourself said that you don't automatically add manual constructors
> and destructors to every struct you write, just in case.

Of course not. When you design a struct type you should already know what it is
supposed to represent and if the represented things lend themselves to dynamic
allocation. If so, better write construction / deconstriction functions. It
hardly adds significant amounts of code

> I don't even understand what you mean by "well-designed code" then. Is
> "well-designed" C code one where every struct has a constructor and
> destructor function (that you always call for every single instance)? Or is
> it something else?

A certain amount of foresight is part of good planning. If you jump straight
into coding without paying attention to possible issues of extending and re-use
you may face some refactoring effort later. This of course goes for anything,
not just changing from static to dynamic allocation in C.

I suspect you once had to refactor some code with a few structs because you
forgot this planning and have now worked yourself into a fret about some
contrived example with "lots and lots" of struct types that need to be adapted.
I don't believe it's a real world case, but if it is, it's poorly planned.

--
robert

Robert Latest

unread,
Apr 27, 2021, 3:26:40 AM4/27/21
to
jacobnavia wrote:
> I used the GC extensively in my debugger and in the IDE for lcc-win.

I'm not sying a GC is always a bad idea. But many of the arguments go along the
lines "C is a bad language because I have to clean up after myself." That part
I don't agree with, especially because it tends to come from people who don't
have much experience in C.

--
robert

Chris M. Thomasson

unread,
Apr 28, 2021, 3:02:40 AM4/28/21
to
On 4/17/2021 10:12 AM, Manfred wrote:
> (cross-posting to comp.lang.c to let the heat fire up)
>
> On 4/17/2021 7:56 AM, Juha Nieminen wrote:
>> I know that this is probably going to piss off avid C programmers,
>> but I don't really care all that much.
>
> Excellent start
>
> [...]
>>
>> I myself happen to know both languages quite well, and in fact I
>> program in both languages as my profession, and I would say that
>> I am very knowledgeable and proficient in both languages and have
>> no trouble in developing in either one. I am of the strong opinion
>> that no, C is not a "simple" language, and that C++ makes so many
>> things so much simpler, easier and safer.
>>
>> I was reminded of this in a quite concrete manner recently when I was
>> trying to help a beginner programmer who had decided to try his hands
>> on C, and wanted to create a simple text editor, which would read a
>> text file, allow the user to edit the text in several ways, and to
>> save the file.
>>
>> It quite quickly turned very frustrating for *both* of us ...
>
> The first comment that comes to mind is that probably part of the
> frustration stems from your approach (no offense) to try and use in C a
> design that would be suitable for C++ instead. Despite their name, the
> two are quite different languages. The similarities are about their
> runtime footprint, not about how they are used.
>
> For example, at a first glance the design of string object is not that
> great for C.
> A common pattern that pops to the mind is to use opaque structs (see
> e.g. FILE*), and have something like:
>
> typedef struct string_t STRING;
>
> STRING* alloc_string(); // creates the empty string
> STRING* set_string(STRING* s, const char* value);
> void free_string(STRING* s);
> STRING* concat_string(STRING* s1, STRING* s2);
> ...
>
> This way there would be no ambiguity about how the STRING object has to
> be used.
> And yes, in C you have to do all yourself - C is a low level language,
> and it is meant for low level stuff. It is hardly the right tool for
> anything meant to interface with a human user - it is (still) great for
> system programming and back-end processing, IMO.
>
> As second, I agree with Jacob that a text editor is a simple tool, but
> it is not easy to make expecially for a beginner.
>
>
> [snip]

Wrt using GC as the everlasting crutch:

https://youtu.be/ao-Sahfy7Hg

Ian Collins

unread,
Apr 30, 2021, 5:11:53 AM4/30/21
to
A tool I use today, but but not one I would use on a production system
without helium cooling!

--
Ian.

Tim Rentsch

unread,
May 11, 2021, 8:14:16 AM5/11/21
to
Keith Thompson <Keith.S.T...@gmail.com> writes:

> Thiago Adams <thiago...@gmail.com> writes:
>
>> On Monday, April 19, 2021 at 8:20:31 AM UTC-3, Tim Rentsch wrote:
>>
>>> Keith Thompson <Keith.S.T...@gmail.com> writes:
>>>
>>>> Manfred <non...@add.invalid> writes:
>>>>
>>>>> [...]
>>>
>>> [discussion of opaque types using pointers to structs whose contents
>>> are not defined]
>>>
>>>>> This is nothing new, as I wrote the entire FILE* infrastructure
>>>>> works this way.
>>>>
>>>> Except that you *can* assign objects of type FILE (if you don't
>>>> mind undefined behavior). The FILE type is opaque in the sense
>>>> that the standard doesn't say what it looks like, but it has to be
>>>> an object type.
>>>
>>> It should be noted that the term "object type" acquired a new
>>> meaning in C11. Before C11, the C standard divides types into
>>> function types, object types, and incomplete types, so "object
>>> type" always meant a complete type and not an incomplete type.
>>> Starting in C11, the C standard divides types into function types
>>> and object types, with object types further divided into complete
>>> object types and incomplete object types. So although it is true
>>> that FILE is an object type, starting in C11 that doesn't mean it
>>> is necessarily a complete object type.
>>
>> Last week I receive a warning in clang complaining about
>> converting void* to function pointer.
>> Searching for this error I found a explanation that C99 or C11
>> I don't recall makes this distinction and this conversion is
>> not guaranteed.
>> The solution proposed on stack overflow was storing a dummy
>> function pointer instead of void*, lets say void (PF)(void)
>> and then cast this dummy function pointer to the final type.
>> I decided just ignore the warning in my code.
>> The justification was that void* could have different size
>> than function pointers.
>
> C does not define the behavior of converting between object
> pointer types and function pointer types (except for the special
> case of a null pointer constant). I believe that's been the case
> since C90.

Right, but the C standard doesn't just not define such conversions.
There is nothing in the Standard that says they may be done. That
is an important distinction.

> The section of the standard on pointer conversions just omits any
> mention of conversions between object pointer types and function
> pointer types. The section on cast operators, however, doesn't
> say that such a conversion is a constraint violation. I find this
> odd. [...]

To me the explanation seems straightforward. The purpose is to
allow an implementation to permit such conversions if the
implementation so desires, while at the same time providing
grounds for rejecting any program that contains such a
conversion, if the implementation would rather do that. Kind of
a "super undefined behavior", if you see what I mean.

Tim Rentsch

unread,
May 11, 2021, 8:18:41 AM5/11/21
to
Richard Damon <Ric...@Damon-Family.org> writes:

> On 4/19/21 4:21 PM, Keith Thompson wrote:
>
>> Thiago Adams <thiago...@gmail.com> writes:
>>
>>> Last week I receive a warning in clang complaining about
>>> converting void* to function pointer. Searching for this error I
>>> found a explanation that C99 or C11 I don't recall makes this
>>> distinction and this conversion is not guaranteed. The solution
>>> proposed on stack overflow was storing a dummy function pointer
>>> instead of void*, lets say void (PF)(void) and then cast this
>>> dummy function pointer to the final type. I decided just ignore
>>> the warning in my code. The justification was that void* could
>>> have different size than function pointers.
>>
>> C does not define the behavior of converting between object pointer
>> types and function pointer types (except for the special case of a
>> null pointer constant). I believe that's been the case since C90.
>>
>> The section of the standard on pointer conversions just omits any
>> mention of conversions between object pointer types and function
>> pointer types. The section on cast operators, however, doesn't say
>> that such a conversion is a constraint violation. I find this odd.
>> (I think we had a long and inconclusive discussion about this a
>> while ago.)
>>
>> It does allow conversions of function pointer types to and from
>> integers, and integers to and from object pointer types, but the
>> results are implementation-defined in most cases.
>>
>> And yes, the rationale is that void* has to be big enough to hold
>> any object pointer value, but function pointers might be bigger
>> than object pointers (more precisely, they might not be convertible
>> without loss of information).
>
> It isn't made a constraint violation because in many cases it does
> work and being able to do it is useful.
>
> It isn't defined to work, because on some machines it just can't
> be done (especially if they are different sizes or to different
> memory spaces like a Harvard Machine)

Also to give implementations the freedom to reject programs that
contain such conversions if they choose to do so, even if there
might be an obvious way to implement it.

Tim Rentsch

unread,
May 11, 2021, 8:29:43 AM5/11/21
to
Bart <b...@freeuk.com> writes:

> On 19/04/2021 23:03, Richard Damon wrote:
>
>> On 4/19/21 4:21 PM, Keith Thompson wrote:
>>
>>> Thiago Adams <thiago...@gmail.com> writes:
>>>
>>>> [converting between object pointers and function pointers]
>>>
>>> C does not define the behavior of converting between object
>>> pointer types and function pointer types (except for the special
>>> case of a null pointer constant). I believe that's been the case
>>> since C90.
>>>
>>> The section of the standard on pointer conversions just omits any
>>> mention of conversions between object pointer types and function
>>> pointer types. The section on cast operators, however, doesn't
>>> say that such a conversion is a constraint violation. I find this
>>> odd. (I think we had a long and inconclusive discussion about
>>> this a while ago.)
>>>
>>> It does allow conversions of function pointer types to and from
>>> integers, and integers to and from object pointer types, but the
>>> results are implementation-defined in most cases.
>>>
>>> And yes, the rationale is that void* has to be big enough to hold
>>> any object pointer value, but function pointers might be bigger
>>> than object pointers (more precisely, they might not be
>>> convertible without loss of information).
>>
>> It isn't made a constraint violation because in many cases it does
>> work and being able to do it is useful.
>>
>> It isn't defined to work, because on some machines it just can't be
>> done (especially if they are different sizes or to different memory
>> spaces like a Harvard Machine)
>
> You can always make it work. On any machine where a raw function
> pointer is very different from an object pointer (say 256 bits vs,
> 64 bits), then a C compiler can implement such pointers via object
> pointers.
>
> In practice, for every actual function, there is an accompanying
> 256-bit value, in data memory, containing its address. A
> 'function pointer' as a C program will see it, is a 64-bit pointer
> to that 256-bit object.

This idea isn't practical due to difficulties in managing
the needed resource allocation.

Furthermore it isn't necessary because there are easy ways around
having to do such conversions.

Tim Rentsch

unread,
May 11, 2021, 8:31:59 AM5/11/21
to
Richard Damon <Ric...@Damon-Family.org> writes:

> On 4/20/21 1:44 PM, jacobnavia wrote:
>
>> Le 20/04/2021 at 18:41, Juha Nieminen a ecrit:
>>
>>> The problem with C is that anything that requires manual construction and
>>> destruction will "contaminate" (can't think of a better word) anything
>>> that
>>> uses that first thing, with the same requirements. All the way down.
>>>
>>> If you have this kind of "string" object that requires manual
>>> construction
>>> and destruction, and you put it as a member of a struct, suddenly that
>>> struct "inherits" the same requirement. Use that struct in yet another
>>> struct and that, too, will "inherit" the same requirement.
>>
>> No.
>>
>> Just have a constructotr and never free anything. Then link with Boehm's
>> GARBAGE COLLECTOR and be done with it!
>>
>> I have already said this in this thread but people will go on arguing
>> about how difficult is to keep the accounting of memory pieces right.
>>
>> And they are right but refuse to see the right tool for theirpurpose:
>> just use a garbage collector and BE DONE WITH IT.
>>
>> I use an automatic car, no passing of gears. Recently I spoke with a
>> taxi driver... "Don't you get bored passing gears all day?"
>>
>> And he acknowledged: yes, My arm hurts at the end of the day. Maybe you
>> are right, should get an automatic...
>>
>> Use an automatic garbage collector and BE DONE WITH IT. And please, do
>> not start with performance problems, in most string applications the gc
>> is completely transparent.
>
> But that only works for a fungible resource like memory. If you really
> need to release the resource right away (like a lock on something) you
> really want RAII.

Or some other resource management mechanism, not RAII necessarily.

David Brown

unread,
May 11, 2021, 8:37:54 AM5/11/21
to
I am only familiar with one system where function pointers are bigger
than other points - the 8-bit AVR microcontrollers, where function
points can be 24 bit (for devices with larger flash) while data pointers
are 16-bit. When the address of a function is taken, the compiler
generates a 24-bit value in memory and uses a 16-bit pointer to that as
the function pointer. Calls via a function pointer thus have this extra
indirection. Not only is is practical (with no challenges about
resource allocations), but it also results in simpler and more efficient
code than using 24-bit function pointers directly.

It is not the only way to handle such issues, but it is certainly a
viable one.
0 new messages