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

"Why does the C programming language refuse to die?"

105 views
Skip to first unread message

Lynn McGuire

unread,
Oct 24, 2018, 3:04:04 PM10/24/18
to
"Why does the C programming language refuse to die?"

https://hub.packtpub.com/why-does-the-c-programming-language-refuse-to-die/

"Portability leads to true ubiquity"

"Programmer-driven memory management"

"Structure is all I got"

"Applications that stand the test of time"

"C: the backbone of our operating systems"

"What does the future holds for C?"

Lynn

Alf P. Steinbach

unread,
Oct 24, 2018, 3:54:55 PM10/24/18
to
Disclaimer: I haven't read that article.

It's an interesting (to me) question whether C++ can be evolved so that
all the intrusive runtime support relative to C, which stands in the way
of using C++ in some contexts, is removed.

The most offensive feature in that respect is IMHO exception handling,
but there Herb Sutter has proposed a much more elegant scheme than the
current, with minimal/no runtime support. More like Eiffel. Nom nom.

Then we have thread support. The current C++11 stuff can probably be
made optional, like a library. After all it started out as a separate
library, in Boost. But the upcoming transactional memory stuff, with
dedicated keywords, sounds much more entangled with the core language,
difficult to remove. And is designed from the start as an integrated
part of the language: no history (that I'm aware of) as a separate
library. So, no guidance and experience in how to factor that out. :(

Dynamic memory management may have some default C++ specific runtime
support, e.g. for all I know some implementation might have an efficient
small objects sub-allocator involved, but one can always just define
`::operator new` etc. to use the C `malloc`-family facilities.

I think that's it, roughly (am I very wrong there?), and if so, then the
main thing that stands in the way of using C++ where C is now used, is
the upcoming transactional memory stuff. Let's hope it's not adopted.
But if history serves as guidance, it will be. :( :( :(


Cheers!,

- Alf

Ian Collins

unread,
Oct 24, 2018, 4:13:58 PM10/24/18
to
On 25/10/18 08:54, Alf P. Steinbach wrote:
> On 24.10.2018 21:03, Lynn McGuire wrote:
>> "Why does the C programming language refuse to die?"
>>
>> https://hub.packtpub.com/why-does-the-c-programming-language-refuse-to-die/
>>
>> "Portability leads to true ubiquity"
>>
>> "Programmer-driven memory management"
>>
>> "Structure is all I got"
>>
>> "Applications that stand the test of time"
>>
>> "C: the backbone of our operating systems"
>>
>> "What does the future holds for C?"
>
> Disclaimer: I haven't read that article.
>
> It's an interesting (to me) question whether C++ can be evolved so that
> all the intrusive runtime support relative to C, which stands in the way
> of using C++ in some contexts, is removed.
>
> The most offensive feature in that respect is IMHO exception handling,
> but there Herb Sutter has proposed a much more elegant scheme than the
> current, with minimal/no runtime support. More like Eiffel. Nom nom.

Yes, I have used C++ without exceptions and RTTI in places (drivers)
which are usually the exclusive domain of C. You can also statically
link the C++ run-time, but that's a bit of an overkill.

--
Ian.

Scott Lurndal

unread,
Oct 24, 2018, 4:23:27 PM10/24/18
to
"Alf P. Steinbach" <alf.p.stein...@gmail.com> writes:
>On 24.10.2018 21:03, Lynn McGuire wrote:

>
>It's an interesting (to me) question whether C++ can be evolved so that
>all the intrusive runtime support relative to C, which stands in the way
>of using C++ in some contexts, is removed.

Sans Exceptions, Sans RTTI and sans STL, we have built two operating
systems and a large-scale hypervisor using C++ (consider it the C++ 2.1
subset) at various large companies (Unisys, SGI, 3Leaf Systems).

All dynamically allocated data structures override operator new/delete
and use pool allocators.

All we needed to add was this:

/*
* This is called by setup64.S to call the constructors of global objects,
* before it calls dvmm_bsp_start().
*
* GNU LD lays out the __CTOR_LIST__ as an array of function pointers. The
* first element of the array (index == 0) contains an integer which
* represents the value derived from subtracting two from the actual number
* of entries in the table. Thus the content of the first element is
* one less than the index of the last entry in the table.
*
* Call in reverse order XXX - why? Check crt0.o for canonical behavior
*/
extern "C" void
__call_constructors()
{
size_t count = *(size_t *)__CTOR_LIST__;

for(count++; count; --count) {
__CTOR_LIST__[count]();
}
}

/*
* G++'s generated code calls this if a pure virtual member is ever called.
*/
extern "C" void
__cxa_pure_virtual()
{
panic("pure virtual function called\n");
}

/*
* This is needed even though we don't ever use the delete operator because
* G++ generates an extra (unused) virtual destructor that calls it.
*/
void
operator delete(void *)
{
panic("operator delete(void*) called\n");
}

/*
* Catch unintended calls to new.
*/
void*
operator new(size_t)
{
panic("operator new(void*) called\n");
}


/*
* G++ generates code for shared library support, even though it isn't
* relevant (or called). That code looks for this symbol.
*/
void *__dso_handle;

/*
* Global object constructors call this to register their corresponding
* destructor. We just ignore it; we never call global object destructors
* because we never exit.
*/
extern "C" int
__cxa_atexit(void (*f)(void *), void *p, void *d)
{
return 0;
}

David Brown

unread,
Oct 24, 2018, 4:25:54 PM10/24/18
to
On 24/10/2018 21:54, Alf P. Steinbach wrote:
> On 24.10.2018 21:03, Lynn McGuire wrote:
>> "Why does the C programming language refuse to die?"
>>
>> https://hub.packtpub.com/why-does-the-c-programming-language-refuse-to-die/
>>
>>
>> "Portability leads to true ubiquity"
>>
>> "Programmer-driven memory management"
>>
>> "Structure is all I got"
>>
>> "Applications that stand the test of time"
>>
>> "C: the backbone of our operating systems"
>>
>> "What does the future holds for C?"
>
> Disclaimer: I haven't read that article.
>
> It's an interesting (to me) question whether C++ can be evolved so that
> all the intrusive runtime support relative to C, which stands in the way
> of using C++ in some contexts, is removed.
>
> The most offensive feature in that respect is IMHO exception handling,
> but there Herb Sutter has proposed a much more elegant scheme than the
> current, with minimal/no runtime support. More like Eiffel. Nom nom.
>
> Then we have thread support. The current C++11 stuff can probably be
> made optional, like a library. After all it started out as a separate
> library, in Boost. But the upcoming transactional memory stuff, with
> dedicated keywords, sounds much more entangled with the core language,
> difficult to remove. And is designed from the start as an integrated
> part of the language: no history (that I'm aware of) as a separate
> library. So, no guidance and experience in how to factor that out. :(

There is no problem with features in the core language as long as they
don't cost if they aren't used.

C++11 threads and atomics, for example, are not a problem - if you don't
use these, they cause almost no overhead because the relevant functions
from libraries are not linked in. The same applies to C11 threads and
atomics. (There is one PITA - thread-safe static initialisation. It is
a cost that is, for the most part, completely unnecessary. There should
be some way to define variables that are static, local to a function,
but initialised pre-main just like namespace scope statics.)

I expect the same will apply to transactional memory stuff - if you
don't use any of these features in your code, the library overhead will
not need to be linked in.

Exceptions are a different matter. The way exceptions work in C++ today
is that they are enabled and active by default, in all functions. You
pay for them everywhere - you have the stack unwind tables that can be a
very significant code cost in embedded systems, and you have the (small
but non-zero) optimisation limitations from the requirements of being
able to unwind the stack. RTTI is in the same bag - it costs by default.

So as far as I can see, it's fine to have new things in the core
language and support libraries, as long as they don't lead to more
linked library code when they are not used.

>
> Dynamic memory management may have some default C++ specific runtime
> support, e.g. for all I know some implementation might have an efficient
> small objects sub-allocator involved, but one can always just define
> `::operator new` etc. to use the C `malloc`-family facilities.
>
> I think that's it, roughly (am I very wrong there?), and if so, then the
> main thing that stands in the way of using C++ where C is now used, is
> the upcoming transactional memory stuff. Let's hope it's not adopted.
> But if history serves as guidance, it will be. :( :( :(
>

The new exception system, if implemented, will help a lot. But there
are other reasons why C is more popular than C++ with -fno-exceptions
-fno-rtti flags in small embedded systems. Part of it is FUD and
conservatism - a lot of embedded code is written in C90, rather than C99
or C11, and C++ is still viewed as "new and complicated".


bitrex

unread,
Oct 24, 2018, 4:31:07 PM10/24/18
to
I get the impression that C is still successful is because what the
people who made it set out to make was a programming language. the later
"Better Cs" were made by people who set out to make a programming
language....because they hate C! ugh! C's the worst! Arrrghhh....

It shows

bitrex

unread,
Oct 24, 2018, 4:43:53 PM10/24/18
to
On 10/24/2018 04:25 PM, David Brown wrote:

>> Dynamic memory management may have some default C++ specific runtime
>> support, e.g. for all I know some implementation might have an
>> efficient small objects sub-allocator involved, but one can always
>> just define `::operator new` etc. to use the C `malloc`-family
>> facilities.
>>
>> I think that's it, roughly (am I very wrong there?), and if so, then
>> the main thing that stands in the way of using C++ where C is now
>> used, is the upcoming transactional memory stuff. Let's hope it's not
>> adopted. But if history serves as guidance, it will be. :( :( :(
>>
>
> The new exception system, if implemented, will help a lot.  But there
> are other reasons why C is more popular than C++ with -fno-exceptions
> -fno-rtti flags in small embedded systems.  Part of it is FUD and
> conservatism - a lot of embedded code is written in C90, rather than C99
> or C11, and C++ is still viewed as "new and complicated".
>
>

it is misplaced conservatism IMO. I see few advantages to using C on
embedded platforms in year of our Lord 2018, even platforms like the
MSP430. C++ makes the lack of intrinsic dynamic memory management on
devices with small amounts of RAM _easier_ to live with, not less so

bitrex

unread,
Oct 24, 2018, 9:20:30 PM10/24/18
to
On 10/24/2018 03:03 PM, Lynn McGuire wrote:
> "Why does the C programming language refuse to die?"

C is "The Rooster." No, no, he ain't gonna die.

<https://www.youtube.com/watch?v=ZUqBglpHTO0>

woodb...@gmail.com

unread,
Oct 24, 2018, 9:56:38 PM10/24/18
to
On Wednesday, October 24, 2018 at 3:23:27 PM UTC-5, Scott Lurndal wrote:
> "Alf P. Steinbach" <alf.p.stein...@gmail.com> writes:
> >On 24.10.2018 21:03, Lynn McGuire wrote:
>
> >
> >It's an interesting (to me) question whether C++ can be evolved so that
> >all the intrusive runtime support relative to C, which stands in the way
> >of using C++ in some contexts, is removed.
>
> Sans Exceptions, Sans RTTI and sans STL, we have built two operating
> systems and a large-scale hypervisor using C++ (consider it the C++ 2.1
> subset) at various large companies (Unisys, SGI, 3Leaf Systems).

G-d willing, Herb Sutter's static exceptions proposal will
gain further support and the details will be ironed out. This
will allow for using exceptions and STL in contexts that have
had to work around them. Someone on Reddit was saying he
thought there could be a compiler or two with support for
this in 2019. That would be great and I hope it will be
GCC as that's my primary compiler.



Brian
Ebenezer Enterprises
http://webEbenezer.net

woodb...@gmail.com

unread,
Oct 24, 2018, 10:43:26 PM10/24/18
to
On Wednesday, October 24, 2018 at 8:56:38 PM UTC-5, woodb...@gmail.com wrote:
> On Wednesday, October 24, 2018 at 3:23:27 PM UTC-5, Scott Lurndal wrote:
> > "Alf P. Steinbach" <alf.p.stein...@gmail.com> writes:
> > >On 24.10.2018 21:03, Lynn McGuire wrote:
> >
> > >
> > >It's an interesting (to me) question whether C++ can be evolved so that
> > >all the intrusive runtime support relative to C, which stands in the way
> > >of using C++ in some contexts, is removed.
> >
> > Sans Exceptions, Sans RTTI and sans STL, we have built two operating
> > systems and a large-scale hypervisor using C++ (consider it the C++ 2.1
> > subset) at various large companies (Unisys, SGI, 3Leaf Systems).
>
> G-d willing, Herb Sutter's static exceptions proposal will
> gain further support and the details will be ironed out. This
> will allow for using exceptions and STL in contexts that have
> had to work around them. Someone on Reddit was saying he
> thought there could be a compiler or two with support for
> this in 2019. That would be great and I hope it will be
> GCC as that's my primary compiler.
>
>
>

This could make C++ a lot more competitive with C and
people like Dan Saks and Odin Holmes who have worked
hard to make C++ work on embedded systems will see
further paybacks for their efforts. It may usher in
a real boost for C++.

bitrex

unread,
Oct 25, 2018, 1:24:41 AM10/25/18
to
It's habitually frustrating that there's no particularly good solution
to error handling on embedded platforms running bare-metal without
resort to hacks.

C++ zero-overhead abstraction lets you do things with even 8 bit
processors that's extremely difficult or impossible to accomplish with
straight C in a reasonable time and thereby, ideally, lets you extract
more value out of limited compute resources; you can make do with a $1
processor instead of a $3 processor or reduce your power consumption
which are extremely valuable things in cost-sensitive applications. but
then they go and hamstring you by adding the overhead back in if you
want exceptions. WTF!

David Brown

unread,
Oct 25, 2018, 4:00:56 AM10/25/18
to
On 24/10/18 22:43, bitrex wrote:
> On 10/24/2018 04:25 PM, David Brown wrote:
>
>>> Dynamic memory management may have some default C++ specific runtime
>>> support, e.g. for all I know some implementation might have an
>>> efficient small objects sub-allocator involved, but one can always
>>> just define `::operator new` etc. to use the C `malloc`-family
>>> facilities.
>>>
>>> I think that's it, roughly (am I very wrong there?), and if so, then
>>> the main thing that stands in the way of using C++ where C is now
>>> used, is the upcoming transactional memory stuff. Let's hope it's not
>>> adopted. But if history serves as guidance, it will be. :( :( :(
>>>
>>
>> The new exception system, if implemented, will help a lot. But there
>> are other reasons why C is more popular than C++ with -fno-exceptions
>> -fno-rtti flags in small embedded systems. Part of it is FUD and
>> conservatism - a lot of embedded code is written in C90, rather than
>> C99 or C11, and C++ is still viewed as "new and complicated".
>>
>>
>
> it is misplaced conservatism IMO.

Oh yes, I agree on that.

There is also entirely reasonable conservatism - when your current
project is build by starting with the previous project and modifying it,
and so on back through the history for the last 20 years, it is hard to
make big changes to the fundamentals like the language. Then you have
things like developer experience - if most of your developers are not up
to speed on modern C++, it makes sense to stick to C for your project.
That of course means that for the next project, they are not up to speed
on modern C++ and it makes sense to stick to C...

A key sticking point that I see is major RTOS's and assorted libraries
like network stacks. These are usually written in C90, often in a
terrible type-unsafe style (lots of void* pointers) with their own
personal sized integer types, their own design flaws that require
special compiler flags or optimisation restrictions on modern compilers,
etc. But they are tried and tested, certified and field-proven - and
that is hugely important.

> I see few advantages to using C on
> embedded platforms in year of our Lord 2018, even platforms like the
> MSP430. C++ makes the lack of intrinsic dynamic memory management on
> devices with small amounts of RAM _easier_ to live with, not less so

Small platforms like the msp430 /do/ have dynamic memory support in
their standard libraries - you have malloc() and free() just like
anywhere else. However, it is unlikely that it makes sense to use it -
you have risks of heap fragmentation, unpredictable timings, and in most
dedicated embedded systems, a memory allocation failure is a critical
failure of the system. Dynamic memory is hugely more complex to analyse
to be sure the system is correct - so static allocation is the norm.
(Alternatives include a very simple malloc implementation and no free,
or dedicated pools for particular uses.)

This applies to C and C++ equally. C++ RAII can make it a lot easier to
track allocations and avoid memory leaks than manual dynamic memory
handling in C, but it does not make any difference to the reasons why
small embedded systems usually avoid dynamic memory.


But perhaps the biggest reason for choosing C over C++ in embedded
systems, especially small systems (rather than embedded Linux or
platforms with MB of memory) is about people. Anyone who has experience
of working with small embedded systems will be experienced with C,
hopefully also assembly, and perhaps also with hardware and electronics.
If you try to get hold of new programmers with good C++ experience,
however, they are likely to come from the world of "big" programming -
multi-GHz, multi-GB, multi-core systems. Usually they simply do not
understand what is important in small systems. They want to use boost,
and unordered_map, and multiple virtual inheritance, and they wonder why
their 30 MHz, 64 KB target can't cope. (I've seen similar things with C
programmers too, but to a lesser extent.)


bitrex

unread,
Oct 25, 2018, 11:56:50 AM10/25/18
to
I guess the intent I was going for by my comment is that modern C++
allows you to more effectively implement data structures and algorithms
which preferentially use the stack to get their work done, via holding
composite objects by value and judicious use of move constructors and
assignment operators.

Yeah at some level you will likely have to hold a resource which is
statically allocated on the heap and not repeatedly allocated and freed
to avoid risk of fragmentation, but you can write everything up the
chain such that it's clear and enforced who the "owner" of that resource
is at any particular time - even if it isn't freed in the lifetime of
the program IMO ownership should still be enforced it shouldn't be a
free-for-all.

bitrex

unread,
Oct 25, 2018, 12:21:03 PM10/25/18
to
This is a "paradigm" I like on embedded systems, resource holders are
encapsulated and move-only, interfaces are defined using the CRTP,
objects which require resource allocation on the heap may only be
instantiated via factory creator methods. Something like:

template <template <typename> class Derived>
class Interface {
public:
template <typename T>
static Derived<T> create()
{
return Derived<T>::create_();
}

template <typename T>
void do_something(const T& t)
{
static_cast<Derived<T>*>(this)->do_something_(t);
}
};

template <typename T>
class Implementation : public Interface<Implementation>
{
template <typename U>
struct Resource {
Resource() : buf_(new U[64])
{}

Resource(const Resource&) = delete;
Resource& operator=(const Resource&) = delete;
Resource(Resource&& other) : buf_(other.buf_)
{
other.buf_ = nullptr;
}

Resource& operator=(Resource&& other) {
buf_ = other.buf_;
other.buf_ = nullptr;
return *this;
}

U* buf_;
};

friend class Interface<Implementation>;

protected:
Implementation() = default;

static Implementation<T> create_()
{
return Implementation<T>();
}

void do_something_(const T& t)
{

}

private:
Implementation() = default;
Resource<T> resource_;
};

BGB

unread,
Oct 25, 2018, 3:43:11 PM10/25/18
to
Or, from the POV of a language designer who actually likes C:
Makes a language, could try to pass it off as a "C replacement";
Doesn't really bother, C still does C things pretty well (and there are
still non-zero cost of using my language in places where C works).

I have also written a C compiler (and a more experimental limited subset
of C++), so these are also options (for use-cases where I am using a
custom compiler).


A downside with doing a "C replacement" is that many of the "flaws" of C
will still exist if one makes a language which can address the same
use-cases:
If one tries to side-step them (by omitting things), then the language
is painful to use;
If one tries to provide alternatives that do similar, but are
"safer"/..., then they come with overhead;
One can't do a "simple" language and aim for this use-case, as by the
time it is "actually usable", then complexity is already on-par with C;
...

While dynamic / "high level" languages can address some use-cases pretty
well, in the context of a "C alternative" they are generally unusable.
Similar generally goes for languages with more wildly different language
designs, ...


Many people add features which require runtime support, such as garbage
collection, which makes it unsuitable for many use-cases (ex: hard
real-time and smaller microcontrollers); and even on big, fast,
soft-real-time uses (ex: typical desktop PC) they are not ideal (the GC
still almost invariably sucks in one area or another).

Things like exceptions are "use with caution"; need to keep ABI cost
minimal. The best scheme I am (currently) aware of is doing it similar
to the Win64 ABI (using using instruction ranges and using the epilog
sequence for unwinding). There is still a cost for storing a table with
per-function pointers (ex: 8 or 16B or so per-function).


Things like bounds-checked arrays are doable, but typically add the cost
of requiring the use of "fat pointers" or similar (either passed in GPRs
or indirectly via reference or the usual "pass struct by value"
mechanism for the ABI, *).

I have used this in my own language (which uses an arrays partway
between the C and Java/C# styles), but it still carries a bit of
overhead vs C-style raw pointers.

*: Mostly leaning against going into "pass struct by value" mechanisms
here (this varies a fair bit between architectures and ABIs).


By the time a type-system is capable enough to do "all the usual C
stuff", it is already about as complicated as what is needed for the C
typesystem (or one pays in other ways).

Though, one can still simplify things partly vs the surface-level
language (this also applies to C), and divide the types into major types
and sub-types.

For Example: ILFDAX
* I: 32-bit int and sub-types.
* L: 64-bit long and sub-types.
* F: 32-bit float and sub-types.
* D: 64-bit double and sub-types.
* A: pointers / arrays / "struct by ref" / ...
* X: 128-bit types (int128, float128, vec4f, small structs, ...).

So, for example, everything which can fit into an 'int' or 'unsigned
int' could fit into 'I'; everything which is internally treated as a
64-bit integer type goes into 'L', ...

This is very similar to the model used internally by Java, but can be
made to accommodate C.

The main remaining "hair" area is that C treats "int" and "unsigned int"
a bit differently in some areas, which may require dealing specially
with unsigned cases in some cases. But, likewise, this is true for
pretty much any language which has unsigned integer types.


OO / object-system, generally needed "de-facto" for a language to be
taken seriously, but even in the simplest cases add complexity over "not
having an object system".

Vs the C++ object system, it is possible to simplify things somewhat
though. Most obvious: Doing like Java and C# and dropping Multiple
Inheritance.


Similarly, one can do like C# and split 'struct' and 'class' into two
functionally distinct entities. This can reduce some implementation hair
vs trying to make both exist.

My case, I still have some complexity (of class-like structs) mostly as
it is still needed to support the C++ subset (though my subset did drop
MI). ( FWIW: Both my custom language and the C++ subset share the same
underlying compiler in this case; and aren't too far off from being
effectively re-skins of the other, *. Though another separate VM based
implementation of this language also exists. )

Similarly, in my case it is also possible to use the "natively compiled"
version in VM style use cases mostly by compiling to a RISC style ISA
and then using partial emulation (such an ISA can still be useful while
also still being relatively simple; and can protect the host's memory
via an address-space sandbox, or possibly with selective use of a
"shared memory" mechanism, ...).

*: Partly as a consequence, many things in one can be expressed in the
other "just with slightly different syntax", and while not quite as
copy/paste compatible with C as C++ is (due to mine using a more
Java-ish declaration syntax), cross-language interfacing is otherwise
relatively straightforward (same underlying ABI). This doesn't currently
target x86 based systems, but could probably do so if needed.


All it really leaves is C having (pros/cons) exposed raw pointers, and
some amount of ugly-named extension keywords (which can be remapped via
typedefs or defines).

And, my case, I also have some features from my other language exposed
in C land via similarly ugly extensions, but generally not used much as
reliance on language extensions is not good for code portability (apart
from cases where the extension is implemented in a way where a plain C
fallback can be provided).

Similarly, between the custom language and a C++ subset, the latter
(also) has the advantage that code written in it will still work in a
normal C++ compiler (and still has the drawback of it being C++ rather
than C).


Combined, a lot of this doesn't really make for a particularly strong
case for trying to replace C.

Chris M. Thomasson

unread,
Oct 25, 2018, 4:36:21 PM10/25/18
to
[...]

Fwiw, a stack based region allocator can be of service in a highly
limited environment:

https://groups.google.com/d/msg/comp.lang.c/7oaJFWKVCTw/sSWYU9BUS_QJ

0 new messages