Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

What do you love about C and hate about C++?


I'm not the parent, but C has a certain simple elegance: everything's an int; those things which aren't ints (e.g. compound structures like structs and arrays) are represented using pointers and offsets, so in a sense they're also ints; except for floats, but meh.

In C++, everything is an int; except for objects; and templates; and lambdas; and classes; and exceptions; and all the other things which keep getting chucked on to the pile.

I like languages with a clear, simple concept behind their operation: lambda calculus, combinatory logic, Scheme, Forth, Joy, Pure, etc. C is a bit gnarly, but passable. C++ is horrible.


Yeah, that phrase "everything's an int" scares me (and I'm a C programmer---been programming in it since 1990). At one time I was playing around with Viola (http://www.viola.org/) (quite possibly the first graphical web browser). The code is a mess precisely because it grasps the "everything's an int" and doesn't let go. Once you get it to compile (and out of the box it doesn't any more) it barely runs on a 32-bit system (which it assumes to an nth degree---sizeof(int) == sizeof(long) == sizeof(void *)) and promptly crashes on a 64-bit system.

Horrible, horrible code.

You can write clean C without casts and treating everything as an integer, but you have to start your code with that goal.


As a C programmer, I would also agree. "Everything is an int" is not a good way to be thinking about things. There's a reason that `intptr_t` exists. Well written C shouldn't rely on any integers being specific sizes or the same size unless you're using the standard types for that purpose (Llke `uint8_t`, `uint32_t`, etc.). Even then, you probably don't need them in a lot of cases, and you should basically never be casting integers into pointers unless you're using the above `intptr_t`, or `uintptr_t`. Even then, you're probably still better off just making your life simple and use a `union`.

That said, I would argue that minimal explicit casts is a pretty good goal for any C programs - I find that crazy casting tends to be a sign something could be done better. But obviously, casts are definitely necessary in some instances, so it's not like "castless" code is guaranteed to be the right way to do things anyway.


To clarify, I should probably have said "integer" to avoid confusion with the C type called "int", or to be more accurate ℤ/w for a variety of widths w.

I didn't mean "C is nice because I can just write 'int' for all the types and it works", I meant "C is nice because it represents data in a very conceptually uniform way: either as integer, or 'pointers' which are themselves integers."

The current world population is an integer, my bank account number is an integer, but that doesn't make it's meaningful to add them together. Values can have the same representation without being the same type :)

> "Everything is an int" is not a good way to be thinking about things. There's a reason that `intptr_t` exists.

From http://en.cppreference.com/w/c/types/integer (top Google hit for `intptr_t`):

    intptr_t
    integer type capable of holding a pointer
They're integers :)

> Well written C shouldn't rely on any integers being specific sizes or the same size unless you're using the standard types for that purpose (Llke `uint8_t`, `uint32_t`, etc.).

I never said it should; but from that same cppreference site:

    int8_t, int16_t, int32_t, int64_t
    signed integer type with width of exactly 8, 16, 32 and 64 bits respectively
These are also integers :)

> you should basically never be casting integers into pointers

Again, I never said you should. I didn't say, or mean, anything about casts.


"everything's an int"

I don't understand this. Floating point values aren't ints. I suppose strings are integers because they're made up of 8-bit chars (which are basically ints), but I don't understand how that is advantageous or helpful.


He was trying to illustrate the concept that in C you deal directly with blocks of memory. It can be useful because instead of worrying about the implementation of the language which you're using, you can think about what the hardware is doing and understand what's going to happen based on that.

So, for example, you know if at offset 0xDEADBEEF there is are 8 bytes which are holding a number that you want to use, you can access that chunk of memory and use it however you want. And you can interpret it how it is appropriate for your use case, for example reading it into a string, or an int.

Being grounded in hardware makes a lot of other stuff quite arbitrary, and it can become a question of style rather than functionality. And by imagining what the hardware is doing you can understand what code is doing that you have never seen before at the lowest levels (all the way to what the registers, FPU, APU, etc on the CPU are doing). That can also help with debugging.


> So, for example, you know if at offset 0xDEADBEEF there is are 8 bytes which are holding a number that you want to use, you can access that chunk of memory and use it however you want. And you can interpret it how it is appropriate for your use case, for example reading it into a string, or an int.

Except these days with strict aliasing that's not true. If you access memory through a pointer of the wrong type, you've probably committed the crime of undefined behavior, for which the compiler might punish you by breaking your code - but probably won't, leaving the bug to be triggered in the future by some random code change or compiler upgrade that creates an optimization opportunity.

Admittedly there are ways to avoid undefined behavior, but still. C gives the compiler a lot of leeway to mess around with your code.


Strict aliasing does not preclude using the bytes however you want. All that does is prohibit type casting in certain situations. By using different syntax you can still interpret the bytes how you want to (even if, in extreme situations, you might have to resort to using a memcmp in order to do so).


'Strict aliasing' is a dangerous optimisation that OpenBSD's gcc-local disables for a reason.


Not really that dangerous, I think it's more of an issue of having code written before strict aliasing rule.

Essentially, what strict aliasing rule requires is that user cannot go crazy casting pointers. Casting float pointer to int pointer is completely wrong. The exception is that it's completely safe to cast values to char pointer or void pointer (and back, but only to original type of a pointer). Objects of different types cannot use the same memory area.

What this usually affects is code that parses external data into a structure. This can be dealt with by use of `memcpy` (to copy data from char array into a structure) instead of pointer casts, which is mostly safe as far C specification is concerned (mostly because C specification doesn't really define stuff like paddings, so you need to be careful about that). There is also an option of using `union` which is allowed by C11 specification (but compilers released with C11 that had strict aliasing optimization didn't break code that had aliasing through union and even documented that).


Not really that dangerous, except for all the code for which it is dangerous?

Of course, you've got me, it only breaks existing code, and doesn't affect code that's written perfectly according to the rules of C and not peoples' intuitions about what pointers actually mean on a hardware level.

It's not even safe to do this:

    struct a {
        int x;
    };
    struct b {
        int x;
        int y;
    };

    struct b b;
    struct a *a = &b;
You have to write b like this:

    struct b {
        struct a inner;
        int y;
    };


>I like languages with a clear, simple concept behind their operation.

So you dont like C because Undefined behaviour complexitys. Im confused now.


As I said, C is gnarly, mostly for legacy and "practicality" (WorseIsBetter) reasons.

Undefined behaviour, declared as in a language standard, isn't too bad; it might forbid some potentially-useful combinations of expressions, but there'll usually be a workaround.

The problem is compilers which allow undefined behaviour by default, rather than aborting with a helpful error message. This puts the burden of avoiding undefined behaviour on the programmer, which is really bad. This might be unsolvable for C, due to the nature of its particular undefined behaviour.

For brand new languages, I'd recommend minimising undefined behaviour as much as possible, but definitely ensure that any that remains can be found statically!


As someone who likes C, I'd like to add a bit of a different look:

The aspect that I love about C is how easy it becomes to read. C offers a (arguably) nice programming interface while not allowing the programmer to hide very many things that are going on. C allows you to make nice abstractions, but those abstractions are still built upon the same basic concepts that C supports, and these abstractions generally don't fundamentally change how the language works, so you can very easily tell the control flow of your program if you simply understand the interactions between the basic parts in use.

Now that said, I'll be the first to say that I think C could benefit from a variety of different features (Some from C++, some from others, and some unique to C), and I could easily give you a fairly long "wish-list" I have for C. But, the clear nature of C is a huge advantage that I think is underrated, and is something that you really can't get back in a language once you lose it.

When you (or I) read a piece of C++ code, there are a lot more details you have to take into account then when you read a piece of C code, simply because C would force the writer to be more explicit in what they are doing in those cases.

Again though, I'll make it clear that I don't think C is the be-all end-all of languages - in particular I'm excited to see how Rust goes - but I do think it has traits that are very nice but largely ignored in most of the current languages in favor of adding lots of different ways to write your code.


I'd argue that with heavy use of macros one can change C quite substantially. Case in point is the linked Cello library.

Pure C with few macros is quite refreshing to read. But it can get hairy quite quickly.


C++ is very complicated. C is basically the simplest possible programming language, and the closest to the API actually presented by the processor (i.e., assembly language)


C is close to the hardware, yes. But simplest possible programming language? No way.


"Simple" was a poor choice of words as it doesn't capture what I mean.

I meant "fewest possible abstractions over the underlying hardware".

BF and Lisp are both much simpler than C, but don't fit what I was trying to get at, as they present a totally different abstraction that is actually quite different from the underlying hardware.

(Actually, so is C, since assembly language is itself an abstraction and processors work much differently than they did in the 70s and 80s... but as a programmer, and not a hardware engineer, I consider asm to be the baseline, zero-abstraction level ;) )


Like C, C++ doesn't force you in any way to have extra useless abstractions over the hardware. The rest of your team do (and C make this way harder).


No, you're still not getting it. It isn't about inefficient abstractions. C++ has lots of zero-cost abstractions, but that's not what the parent is talking about.

He's saying that C is simple because it offers a small set of mostly orthogonal features. You get structs, functions, pointers and integers, basically. You don't even have opaque 'String' types or any of that. It's all simple, orthogonal bits.

In C, if there are no macros involved, everything you read in code is arithmetic, function calls or pointer dereferencing. There is no overloading, there are no automatic secret implicit constructors, there are no destructors, no garbage collection, etc.

C++ has a huge amount of behaviour, a lot of it very abstract compared to C. For example, vtables. How are they implemented? That isn't clear at all from reading C++ code. You have to go look at the generated code. If there are vtables in C you have explicitly written

    struct vtable {
            foo_function *foo;
            bar_function *bar; };
    struct a {
            struct vtable vt;
            ...
    };


The run-time cost of C++ abstractions is also NOT what I was talking about.

I was talking about how C++ requires more communication with your team, because some "abstraction" features you might not want (RAII, RTTI, polymorphic dispatch, templates, exceptions) are a lot more easy to introduce than with C.


That would be Forth




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

Search: