Memory management in C programs (2014)
nethack4.org
nethack4.org
I would not consider C++ code that eschews exceptions "modern". To eschew exceptions almost always entails giving up RAII, and if you give up RAII you've done yourself a huge disservice: you're now stuck manually managing memory and resources, and suffering all the bugs that come with that. While RAII does not outright prevent these bugs (which is one of the reasons I'm a huge fan of Rust), coding along its principles greatly reduces the risk that you will run into problems.
If you're wondering: giving up exceptions means a constructor has no way to signal failure, as the result of a ctor in C++ is always either an exception, or a constructed object. Most "no exceptions" C++ code I've seen opts to construct what I call "zombie objects"; internally, the object tracks that an error has occurred, and all uses of the real object must be first checked against that internal error flag to ensure the object isn't a zombie (or if it is, then error). The object is effectively a null pointer, and has all the trouble that entails.
(I also won't pretend that exceptions aren't without problems; in particular, the argument against them because you can't tell, at a particular point in a function's code, if an exception can occur, or where it would be caught, is completely valid, but no different in many popular languages, such as Python, C#, and in some ways, Java. However, I think the advantages of RAII — and exceptions — outweigh their disadvantages.)
> It just has to be written so that most functions manually propagate error codes they receive
When I've had to write C, I've found this to be exceptionally tedious to do correctly, and all too easy to ignore.
How so? RAII works just fine when you return errors.
At least in C++ (and many other OO languages), I find the downsides of having these Jekyll and Hyde classes outweigh any benefits that may come of ignoring exceptions.
Note: I don't actually know C++ very well. It's possible there's a more idiomatic way to accomplish this. In particular, another wrapper class could be created that owns an instance of the trivially-constructed instance mentioned above. The factory function could then take an instance of the wrapper class and then modify and return an instance of the class you actually want to obtain.
> Constructors are resource allocation, so they can always fail.
Which is false.
I see your comment above that stack overflows essentially never happen, but having watched it happen, I disagree.
Because the only correct behavior here is to terminate the program immediately, it doesn't matter in the discussion of whether to use exceptions or not.
This is especially true when third party code is involved. C++ is hard enough to get right without exceptions; dealing with failure in bodies of code that you didn't write and maybe don't have control over (or even source code of) is death on wheels.
If that means the code I'm working on isn't modern . . . I think that's fine. But the first volume (of three?) of Herb Sutter's Exceptional C++ should be enough to convince anyone that dealing with exceptions "correctly" is time better spent eschewing exceptions and restructuring your code so you can do worthwhile hard things.
In what manner? If you use exceptions, you must (IMO) use RAII, or yes, it will be painful. Every example of "painful C++" I've seen involved someone ignoring that, and that is fighting the language. I understand that's a bit of a strawman argument, but you didn't elaborate enough in your post to debate it.
If you are using RAII, exceptions shouldn't be terribly hard to "get right"; an exception propagating up the stack will free resources as it goes. (This is the same behavior, again, as numerous other languages; except in C++, I have the benefit of RAII, in others, I generally do not.)
> dealing with failure in bodies of code that you didn't write and maybe don't have control over (or even source code of) is death on wheels.
It's peculiar that I only ever hear this argument against C++, when Python, C#, and to an extent Java¹ work in the same manner. Were you writing a C++ webserver, for example, I expect that you would do exactly what my current job's Python webserver does: if an uncaught exception makes it to the main request handler, it is caught in a catch-all, duly logged, and a 500 served in response. This is appropriate both in Python and C++ IMO. (Whereas in the hypothetical C++ side, I gain the benefits of static typing and RAII that I can never have in Python.)
In my day-to-day Python, we quite often learn about uncaught exceptions — from third-party code that I didn't write and maybe don't have control over — in production; sometimes, we will add a handler for that particular exception where appropriate (it was a bug) or fix the underlying symptom (the exception indicates a larger problem, and allowing it to propagate to a good catch-all point was appropriate, as was 500'ing the request).
But again, "death on wheels" is not really something I can provide worthwhile argument against. Perhaps where third-party code has caused pain (at least, for me) is when a very low level (say, socket error) propagates through the stack; there might not be a good way to catch these beyond "catch everything", but again, that's not unique to C++. The biggest problem I've had with these is that they lack context: what caused this socket error? Or, if some library catches & re-throws its own exception class, too often do they discard the inner error, and I lack the root cause (a different lack of context). But you see this in Python as well. Error codes are perhaps the worst, as they will quite often force you to lose context; do you expose error codes for all of your inner error conditions? (And theirs, transitively?). (I've never seen a non-exception C++ or a C codebase use anything aside from error codes as a substitute.)
I guess he's referring to noexcept/basic/strong exception guarantees. Yes, writing code with strong guarantees is hard, but what makes it hard is that you have to write your code "transactionally", i.e., either 1) prepare changes and "commit" them in one go, or 2) roll back changes if an exception occurs.
But writing code with "strong guarantee" is just as hard, if not harder, with error codes.
Yes, you can still use RAII for rollback/cleanup in combination with error codes, but then you get the worst of both worlds: the (minor) complication of writing RAII classes AND code cluttered with manual error-checking.
This works fine. I think the worst bug was an exception thrown from the initial state which lead to an infinite loop.
You can also use it to recover parts of the processing with sane defaults. String parse integer exception? Use constant 0 instead if that makes sense, etc etc.
I don't see why people make exceptions in c++ into a big deal and a big mystery, in other languages people just use them, the only caveat in c++ is throwing in destructors but that's easy to remember. In one place I worked they had meetings for over a week to discuss whether it was ok or not to throw exceptions in a constructor or not T_T.
In hindsight, he should have coded the logic/business part in "modern" C++ with exceptions and implemented thin adapters to the GUI part. That's what I did in another piece of code, and am extremely happy with the outcome.
It's not even hard to get them right, "the lightbulb" lit up for me after reading the chapter on exceptions in Stroustrup's TC++PL.
They said that their decision was primarily legacy-motivated (in short, they were fearing that introducing exceptions in a non-RAII codebase would lead us to a long period of instability).
I'll add that in some situations (e.g when freeing resources) the control flow gets very convoluted, and you don't want to deal with directly. See: Andrei Alexandrescu - Declarative Control Flow ( https://www.youtube.com/watch?v=WjTrfoiB0MQ )
> When I've had to write C, I've found this to be exceptionally tedious to do correctly, and all too easy to ignore.
Indeed. I write Go all day and I type "if err != nil { return nil, err }" so often I could scream.
Just curious; I used a combination of strong/weak reference counting when I dealt with this. It wasn't fast, but it did appear to be less prone to implicit leaking.
How much code have you written with exceptions? A lot of features you don't miss if you don't use them, but once you're used to use them it's very frustrating to live without them. Back when I wrote C I never thought "gee it would be nice to use an anonymous function here", but now that I'm used to them it's very frustrating to not have them.
RAII is generally useful without exceptions.
I have stumbled more recently over another case where the constructor/destructor paradigm in C++ is not ideal, since in some cases it 'encourages' constructing objects on the heap, just for the reason that allocation and initialisation are the same operation, I haven't thought this completely through yet, but bear with me:
Basically I'm trying to write code in a way that minimizes heap allocations. In the extreme case I have just one big 'application object', and all other objects are embedded in this application object. For variable number of objects, pools/arrays with the max capacity are embedded. If taken to the extreme, this model only does a single allocation at application startup (or it could even live on the stack).
However, there are systems which need to be initialized later, and in a specific order (for instance rendering, audio, input, etc...). The objects for these systems have already been created and had their constructor run, but they are not ready for use. That's where I need separate initialisation steps, unless I want to put those objects back on the heap. And in reverse, I need to teardown graphics, audio, input before the big application object is destroyed (and the destructors of the embedded objects are called).
Now I don't think that this extreme case of trying to minimize dynamic allocations down to 1 is particularly useful in real world code, it's more of a though experiment, but in this case, the good ol'e C way of separating allocation/deallocation from initialization/teardown is actually more useful.
Unfortunately I have stumbled over C++ frameworks which basically require to create all objects on the heap, which in my opinion is a design fault. A framework should not dictate to its users whether objects are created on the stack, on the heap, or are embedded in other objects.
I wouldn't want to be doing anything custom around allocation without the support of a type system though. Having allocated-but-not-fully-initialized objects hanging around and looking exactly like fully-initialized objects is a recipe for disaster, and while it may be possible to keep track of things yourself through constant vigilance, the cognitive load just isn't worth it IMO. So if I had to do that kind of thing in C++ I'd probably look at having a custom allocator and some way to pass hints to it, rather than doing allocation and initialization separately in "normal" code. (Well, in reality I wouldn't use C++)
> Unfortunately I have stumbled over C++ frameworks which basically require to create all objects on the heap, which in my opinion is a design fault. A framework should not dictate to its users whether objects are created on the stack, on the heap, or are embedded in other objects.
Depends on the use case IMO. The flexibility of being able to control all your objects is valuable, but it doesn't come for free. One can't really generalize about what's idiomatic C++, because there's such a broad range of users.
In games, you have a small number of functions that can tolerate failure, probably relating to IO. Everything else instantly explodes.
Partly because of the malloc/free issue, you don't just "throw and forget" like with real exceptions. Instead you jump a short way up the stack, using it as an extra mechanism for error handling inside a module -- living side by side with error returns.
And I found myself using the mechanism quite a lot. To the point where writing a new error-returns based function gave me that itch where I felt this is mistake and I would end up refactoring to the TRY mechanism anyway.
There's sometimes a great insight available in viewing your allocation hierarchy on a running program, too.
"""
The DevTeam would like to welcome its latest members. Both of these folks should be familiar to the members of the NetHack community:
Alex Smith, who created the AceHack and NetHack 4 variants. Alex is an expert on the inner workings of the game and the ways in which they can be exploited.
Patric Mueller, who is probably best known as the creator of the UnNetHack variant. Patric also created NetHack-De (German translation of NetHack) and has considerable involvement in the Junethack tournament. Before contracting the dreaded coding bug, Patric was a long time player, having started out on NetHack 3.0 on the Amiga back in the late 80s.
Alex has indicated that, at least initially, he'd be concentrating on improved interface, improved internals, and new features in areas that don't affect the game's gameplay, drawing on some of the features introduced in AceHack and NetHack 4.
Patric's initial focus will be to identify and incorporate changes from a variety of variants into NetHack, in order to expand the gameplay while keeping NetHack's spirit and appeal intact.
Please join us in welcoming Patric and Alex to the DevTeam as we start plans for the next major release.
For the DevTeam, Mike Stephenson
"""
case OPTTYPE_KEYMAP:
str = malloc(1 + sizeof "submenu");
strcpy(str, "submenu");
return str;
There isn't a good reason for this not to be return strdup("submenu");
is there?That's unnecessary. But then again I can't really blame them. It isn't always clear when you have to do that. Also a previous version might have used strlen() instead of sizeof.
In GCC and Clang, there is the "cleanup" attribute which runs a user-supplied function once a variable goes out of scope, so you can have scope-based destructors:
static void do_cleanup(char **str) {
free(*str);
}
void f(void) {
char *s __attribute__((__cleanup__(do_cleanup))) = malloc(20);
// s gets freed once the function exits
}For me, C is the gateway to the computer. It's ubiquitous. It's deceptively simple. I can literally write anything I want in C. It's an exciting thing to have an open main.c file sitting in front of me.
That must be how JS developers feel about the browser. The browser is a natural way to start learning programming for someone new to it these days. When I was growing up, we didn't really have JS and the web was brand new. I had to learn from QBasic's "Help" menu!
I bet there's an age correlation, too. I bet over a certain age, it's way more C programmers, and under it is way more JS people. (And above us is probably FORTRAN and above them is probably COBOL.)</div>
JavaScript, for example requires a browser to compile, which requires gcc or clang to compile. Python, Rust, etc all depend on programs written in C and C++.
Go is one recent exception of a popular language whose main implementation is completely self hosted. Though, Go doesn't host other popular languages.
C (this view is usually presented as only about c) isn't fundamental or bedrock in any way. Most of our current stacks just happen to be written in it.
The original Mac OS for instance was written in Pascal and C. The most popular open source compilers are written in c++. Most browsers are written in c++ not in c.
And there's no reason these days you couldn't rewrite llvm (a set of compiler libraries and compilers mostly written in c++, not that rust compiler uses llvm for code generation but the frontend is in rust) in haskell or java or javascript(they'd be slower but they would work). Haskell has useful features for writing compilers and a lot of language analysis libraries already. If you re-wrote in in rust it'd be just as fast.
Hell this guy (https://github.com/jameysharp/corrode) wrote a rust-c source-to-source compiler in literate haskell. Also in a few years servo will be a full browser in rust and Firefox is already adding components written in rust.
Other then os interface & c binary api linking there is nothing special about c or c++.
Sure, there's nothing special about the languages themselves, but _currently_ C and C++ are the foundation for (almost) all other languages. In theory you could rewrite llvm (or use a different complier), but I don't see that happening any time soon. That's why I call it bedrock, because it would be very hard to change.
(Though, I do wonder when llvm will stop depending on Python 2 :)
It wasn't self-hosted in the beginning, only since version 1.5 IIRC.
Nonsense. That's as absurd as claiming assembly is the only way to go if you want to learn real programming.
> but I like C better and it's more fun.
Which is the only reason you think the above, nothing to do with reality, everything to do with personal preference.
Only 90's kids will get JS this.
I like being a JS dev. It's a very fun runtime to poke around... I'd almost prefer all apps to live within the heavily sandboxed browser. It's the best effort made toward portability and it's been a community effort. :x
If you can solve the problems in K&R, a good book to work through next is Computer Systems: A Programmer's Perspective [0].
Coursera used to have a course called The Hardware Software Interface [1] which covered the same material, but it doesn't seem to be availble right now.
[1] https://www.coursetalk.com/providers/coursera/courses/the-ha...
Actually, I think assembly language would probably be the next age "peak". If you know Asm you'll be far better at C; things like pointers and indirection immediately make sense. The effect is weaker, but still there in the other direction.
(FYI, FORTRAN predates COBOL by a few years.)
Be sure not to confuse cause and effect there: if you managed to learn ASM, you must not be the sort of programmer who is confused by pointers and indirection.
Granted I am a 70's kid that started with Timex BASIC and Z80, but we learned pointers in one afternoon drawing boxes and lines on a piece of paper and that was it.
When Intel made their 16bit cpus they still could address 20bit of memory so they decided to create the DS (current segment) and DX (offset) register.
The problem with far pointers is that they could have different values but point the same physical address and C still carries that legacy with it in the form of undefined behaviour which makes everything needlessly complex.
At my university, the Electrical & Computer Engineering curriculum teaches freshman assembly language before C. All of the ECE students understand pointers immediately. The Computer Science curriculum teaches C++ (with raw pointers) before assembly language. Pointers are significant source of frustration for many students in CS.
A lot of recent issues with compilers "introducing security issues" has come from this sort of understanding of C (overflows, other undefined behavior, etc). The places where C differs from just writing the assembly yourself are really really hard.
Some languages do a good job of creating an abstracted universe above the hardware that plays by its own rules. This allows the language/runtime to map programming constructs to hardware behaviour in highly inventive ways.
C never was such a language, it's hardware abstractions are -- by design -- as leak-free as a fishnet. And yet compiler writers feel they need to get creative.
Rumors are coming to town that those druids are now gathering on the valley between the Misty C++ mountains, Ada forest and Rusty valley.
Seriously? Why would anyone use sprintf() in 2013 without knowing the size of all possible arguments.