Hacker News new | comments | ask | show | jobs | submit login

A quick word of warning: be sure to treat C and C++ as two completely separate languages that just happen to have similar syntax. Yes, you can use C++ as "C with classes" (I and many others sure have at times), but you're doing yourself a disservice most of the time if you do.





Huh. I always perceived C to be a subset of C++.

Current versions of C have some features that are not present in current versions of C++ (e.g. designated initializers).

But that's not the point. C and C++ are in practice used completely differently, so writing C++ using C concepts with some additions rarely happens in the wild.


... except in the firmware space, where important C++ mechanisms (especially RTTI and exceptions) tend to be unavailable, giving rise to a (useful, powerful, fun) hybrid of the two.

C++ is largely backwards compatible to C, but idiomatic code is very different in modern practice between the two. A somewhat dated blog post I wrote on the topic a decade ago:

https://blog.directededge.com/2009/05/21/c-and-c-are-not-the...


C enum values are convertible from int; C++ enum values aren't. This is one of the biggest differences in fairly idiomatic C code and has been the case for a very long time (i.e. not dependent on newer C features not being in C++).

    #include <stdio.h>
    
    typedef enum EFoo {
        FOO_A = 1,
        FOO_B = 2
    } TFoo;
    
    int main()
    {
        TFoo foo = FOO_A | FOO_B;
        printf("foo = %d\n", foo);
    }
This compiles in C but not C++.


That's not the same code, is it? GP's code does not seem to compile in C++: https://godbolt.org/z/5oAIYE

But it does in C: https://godbolt.org/z/zdTKiE


Try creating a foo https://godbolt.org/z/AlHDhg

I like to think what is JSON to Javascript is C to C++ (not capability wise). JSON and Javascript are completely, entirely different things even though JSON is technically a subset of Javascript (every valid JSON sentence is a valid Javascript sentence). C and C++ are extremely different languages -- mostly due to cultural reasons -- even though every valid C program is a valid C++ program (not strictly true, C and C++ diverged slightly, but you got the point). I, for example, write C-ish C++ and compile it with g++ but call it "C" because it's "nothing like C++" and "it's basically C with some extra keywords". I know this comment sounds absurd but C++, especially modern C++, is a very complicated beast with a complex ecosystem and culture attached to it. If you inherit a C program and add one C++ line and compile it with g++, even though it's "technically" C++ now, I really wouldn't call it C++. The way you solve problems in C and C++ are extremely different, and this matters.

Can I be pedantic? If I'm wrong someone will correct me and I'll learn something.

Json is not exactly a subset because it supports representation of numbers of any precision. JavaScript will convert to an IEEE 754. But it's up to a json deserializer to decide how many decimals to use. I think.


I mean sure, doesn't change my point (pedantically C isn't a subset of C++ either). That was just a metaphor.

That used to be true, but becomes less true with each new version of the respective language specs. Sometimes the differences are obvious because legal identifiers in C can be keywords in C++, sometimes the difference is subtle, like with the rules regarding type punning.

But the major point is that there are almost always safer or more ergonomic ways to do things using C++ features that are not present in C.


imho, c :: c++ == ipv4 :: ipv6 (where '::' should be read as 'is to' relation)

FWIW I think he's wrong. Basically, when writing C, you should write code the same as if you'd write it in C++, except that you're using C.

Because sometimes that's annoying, things can get less type safe, but in your head, there's a C++ program that you're representing.


Programs written in this mindset might be the number one reason why C has a bad reputation. Of course, if you just emulate what other languages automate for you, you should better write in these languages.

But in reality, why C is still the best programming language for large projects (IMO) is exactly that the programmer is allowed to choose a suitable structure, such that the program can fulfill the technical requirements. Other languages force the project into a structure that somehow never fits after a couple thousand LOC.


What.

What good programs are written in C that don't have well-structured memory management the way C++ does it with RAII?

Edit:

"But in reality, why C is still the best programming language for large projects (IMO) is exactly that the programmer is allowed to choose a suitable structure, such that the program can fulfill the technical requirements. Other languages force the project into a structure that somehow never fits after a couple thousand LOC."

Yeah, this doesn't make any sense. The reason is, C++ doesn't impose anything on your program structure that C doesn't, while C, with the limitations it has, imposes a tax on all sorts of ways of structuring your program.

For example, you can't practically write a program using a futures library (such as the Seastar framework) in C. And every program you write in sensibly written C can be translated to C++. The exception might be really really small-scale embedded stuff that doesn't allocate memory.


> What good programs are written in C that don't have well-structured memory management the way C++ does it with RAII?

MISRA C standards, popular in embedded projects especially automotive, ban the use of memory management altogether.

The whole point of RAII is that the compiler manages it for you as far as it can. This is impossible in C because you have to do it manually. You might end up writing malloc() at the top and free() at the bottom of functions but that's the opposite of RAII.


Note that they ban the use of dynamic allocation at run-time except via the stack, but that you are still allowed to allocate from the heap as long as that heap allocation is static for the life of the system. This avoids a whole host of problems related to heap exhaustion that result from allocation timing causing heap fragmentation.

It also eliminates a whole lot of uncertainty in the timing.

If you're running in an automotive environment, you're probably real time; that is, you have to finish your processing before, for example, the next cylinder comes into firing position. You have to hit that, for every cylinder of every rotation of the engine, for any rotation rate that the engine is capable of reaching. You can't be late even once.

Now in the processing you have a malloc call. How long will the call take? Depends on the state of the heap. And what is that state? Depends on the exact sequence of other calls to the heap since boot time. That's really hard to analyze.

Yes, you can get a memory allocator that has a bounded-worst-case response time, but you also need one that absolutely guaranteed always returns you a valid block. And the same on calls to free: there must be a guaranteed hard upper bound on how long it takes, and it must always leave the heap in a state where future allocations are guaranteed to work and guaranteed to have bounded time.

And, after all of that, you still have a bunch of embedded engineers scratching their heads, and asking "explain to me again how allocating memory at all is making my life easier?"

So embedded systems that care about meeting their timings often allocate the buffers they need at startup, and never after startup. Instead, the just re-use their buffers.


RAII is a disaster. Piecemeal allocation and wild jumping across the project to do all these little steps (to the point where the programmer cannot predict anymore what will happen) is not the way to go.

Then all the implications like exceptions and needing to implement copy constructors, move constructors, etc. in each little structure.

As to what C project doesn't just emulate RAII: Take any large C project and you will likely find diverse memory management strategies other than the object-oriented, scope-based one. Also, other interfacing strategies than the "each little thing carries their own vtable" approach. The linux kernel is one obvious example, of course.

But I also want to reference my own current project since it's probably written in a slightly unusual style (almost no pointers except a few global arrays. Very relational approach). https://github.com/jstimpfle/language. Show me a compiler written in RAII style C++ that can compile millions of lines of code per second and we can meet for a beer.

> The reason is, C++ doesn't impose anything on your program structure that C doesn't

Of course you can write C in C++ (minus designated initializers and maybe a few other little things). What point does this prove, though?


I guess you're the yin to my yang, because I've got a compiler written in C that doesn't use any global variables at all: https://github.com/srh/kit/tree/master/phase1

It wasn't really implemented for performance, and maybe the language is more complicated -- no doubt it's a lot slower. On the other hand, I can look at any function and see what its inputs and outputs are.


My compiler isn't optimized for performance, either! I didn't do much other than expanding a linear symbol search into a few more lines doing binary symbol search. And I've got string interning (hashing).

I've mostly optimized for clean "mathematical" data structures - basically a bunch of global arrays. This approach is grounded on the realization that arrays are just materialized functions, and in fact they are often the better, clearer, and more maintainable functions. If you can represent the domain as consecutive integer values, of course. So I've designed my datastructures around that. It's great for modularity as well, since you can use multiple parallel arrays to associate diverse types of data.

But anyway, your language looks impressive I must say.


What are your thoughts on D or Rust as they compare to C++?

Rust makes intuitive sense to anybody that knows C++ and Haskell (deeply enough to have seen the ST type constructor). There are some natural, healthy memory management decisions you might make in C++ that you can't do in Rust, but that's life. The obvious example would be if you want one struct member to point into another. I like Rust traits or Go style interfaces over classes. cppreference is far far better than any Rust documentation, which aside from Rust by Example, is an unnavigable mess. Oh, and it would be nice if C++ had discriminated unions.

I don't know enough about D to really comment on it (I read the black book on it 9 years ago, and the examples crashed the compiler), but... it has better compile times, right? There's a module system? I'd have to look at the D-with-no-GC story, figure out how hard it is to interop with C++ API's. I think I'd have better feelings about D (or C++) if it didn't have class-based OOP.


This seems like the wrong direction; C++ style projects are either more heavily indirected or make heavier use of compile-time reasoning with the type system. While you can pretend that a structure full of function pointers is a vtable (and the Linux kernel does a lot of this), it's not really the same thing.

Treating C as a sort of "portable assembler" is a lot better, although it runs into UB problems (see DJB on this subject).


The view of C programming that I'm describing is mostly compatible with the concept of treating C as a portable assembler.

I think there is a world of wild and crazy C++ (like, boost::spirit-grade, or std::allocator-using) that you're imagining, that is not what I am thinking of. If you took C, and added vector<T> and hash_table<K, V>, added constructors and destructors so you don't have to call cleanup functions, you'd get a language which most sensible non-embedded C programs would map to, which then maps upward to C++.

Maybe some templated functions like std::min<T> and std::max<T> and add_checking_overflow<T> would be nice to have too.

Edit:

An example of where I think properly written C diverges from portable assembler is things like here: https://github.com/Tarsnap/tarsnap/blob/master/libcperciva/d...

It depends on whether you think ELASTICARRAY_DECL is within the scope of portable assembler. (It gives you type safety!) (And I don't know what advanced assembly languages can offer in terms of that -- maybe they do too.)




Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact

Search: