Live data from Hacker News

Tips for stable and portable software

begriffs.com

51–60 of 63 posts

Re: Tips for stable and portable software

#51

I've currently involved with a system, written in C, which has been going for 30 years: GAP - https://www.gap-system.org While I write a lot of C, I immediately disagree with the idea that C has a "simple (yet expressive) abstract machine model". Every so often we find a bug which has been present for over a decade, because some new compiler has added a (perfectly legal by the standard) optimisation which breaks some…

> Also, while POSIX is a nice base, it stops you using Windows, and also almost every non-trivial program ends up with a bunch of autoconf (which has to be updated every so often) to handle differences between linux/BSD/Mac.

I agree the lack of compliant POSIX on Windows is annoying. However, unless you rely on 3rd-party libraries, you can use #ifdef to write OS-specific code without autoconf.

Re: Tips for stable and portable software

#52
post #36

Earlier quoted context omitted.

The same reason why my default stack is still Java/Kotlin with Spring and Hibernate. A stable environment, stable runtime that is guaranteed to not change too much and has a culture of backwards compatibility.

How are you liking Java with all the versions that are dropping. Are you still on 8? Correto? Or are you keeping up? and do you see the value in the features that are dropping.

java.time is really nice, so I use Java 11 (2018) on my servers. If not using java.time, Java 8 (2014) or later is fine. But I use Clojure, not raw Java. I haven't had any issues with upgrading. If a server works with 8, it works fine with 11 (in my experience).

Re: Tips for stable and portable software

#53
post #32

Earlier quoted context omitted.

If it were so easy, there would be already specified a subset of C without undefined behavior and you could be able to automatically check your code against it.

My point was only that C programmers should be keenly aware of the pitfalls of undefined behaviour, rather than blithely ignoring it. I've been surprised by the sloppiness of some developers on this point. > a subset of C without undefined behavior There are various projects out there that let you produce C code guaranteed to be free of undefined behaviour, but they're not 'quick fix' solutions, so they're not widely…

What does "keenly aware" even mean? For example: any time I add or subtract two signed ints, undefined behavior can happen. Now what. Must I pepper the code with bounds checks (which are prone to UB too if not done carefully)?

Anyway, any complicated thing that can be easily ignored, inevitably will be.

Re: Tips for stable and portable software

#54
This is mostly good advice. I don't love configure scripts, I don't agree with the heavy reliance on POSIX if you intend to be compatible with Windows, and I don't love the fact that the author recommends third party data structure libraries that they haven't actually used. For container libraries in C, you really have to use them to get a feel for their usability (this sounds like a tautology but it's not.)

I disagree strongly with one recommendation. This is just an example, but it holds for larger API design in general:

> we could add a fallback to reading /dev/random. [...] However, in this case, the increased portability would require a change in interface. Since fopen() or fread() on /dev/random could fail, our function would need to return bool.

No, definitely not. It is dangerous to expect the application to sanely handle the case of randomness being unavailable when it is never going to occur in practice. On all POSIX platforms, /dev/random exists and will block until sufficient entropy is available. Something would have to go seriously wrong for this to fail. This is so rare that any error handling code for it will never be tested. The most likely outcome of forcing the caller to handle it is that the return value is ignored or improperly handled and the buffer is used uninitialized, leading to a security vulnerability.

My recommendation instead would be to error check your fopen() and fread() calls within get_random_bytes(), and print an error and abort() if they fail. This way if someone's system is improperly configured and /dev/random doesn't work the program will just crash. Same goes for macOS's SecRandomCopyBytes() and Windows' half a dozen calls to use an HCRYPTPROV. This way you still return void and there is no danger of callers improperly handling errors.

In general, unless you're writing safety-critical software, it's fine for your code (or even library code) to abort() in these sorts of exceptional situations when there is no reasonable or safe way to handle the error. If someone truly wants to handle the error, they can just not use your API and do it manually.

Re: Tips for stable and portable software

#55
post #5

One day, maybe when I am retired, I am going to develop a programming language-agnostic algorithm specifying language with which you can generate code for programming languages ;-). A kind om Mathematica, but than for software.

You're basically describing a virtual machine. Compile to bytecode and the runtime JITs (or AOTs) it on the host. It is an idea decades old.

Re: Tips for stable and portable software

#56
post #53

Earlier quoted context omitted.

My point was only that C programmers should be keenly aware of the pitfalls of undefined behaviour, rather than blithely ignoring it. I've been surprised by the sloppiness of some developers on this point. > a subset of C without undefined behavior There are various projects out there that let you produce C code guaranteed to be free of undefined behaviour, but they're not 'quick fix' solutions, so they're not widely…

What does "keenly aware" even mean? For example: any time I add or subtract two signed ints, undefined behavior can happen. Now what. Must I pepper the code with bounds checks (which are prone to UB too if not done carefully)? Anyway, any complicated thing that can be easily ignored, inevitably will be.

> What does "keenly aware" even mean?

Keeping the threat of undefined behaviour in mind, and taking steps accordingly, rather than complacently ignoring it. C is a highly unsafe language, and the programmer shouldn't forget this.

> any complicated thing that can be easily ignored, inevitably will be.

The demonstrable inability of C programmers to write correct code is a strong argument against the widespread use of C. Even old languages like Ada show that you can use a language much safer than C and still achieve solid performance. Languages like Rust are making further progress on having safety, performance, and programmer-convenience, all at once.

If you use an ultra-safe language like verified SPARK Ada, the language doesn't even allow you to, say, forget to check whether a denominator is zero, or to forget to protect against out-of-bounds array access.

> Must I pepper the code with bounds checks (which are prone to UB too if not done carefully)?

Not necessarily; a tool can help check for undefined behaviour. Static analysers, GCC flags, and tools like Valgrind, can automatically check for out-of-bounds array access, divide-by-zero, or attempting to dereference NULL. [0] Adding your own runtime assertions isn't a crazy idea though, especially for dev builds. If this were the norm in C programming we'd have fewer security vulnerabilities.

C lacks the kind of runtime checks that are 'always on' in languages like Java and C# (out-of-bounds, divide-by-zero, etc). That's not because such checks don't apply to C code, it's because of the minimalist C design philosophy. You have the option to add your own checks, or use tools to do so automatically, but if you develop without any checks anywhere you should expect to have more bugs. Java added them for a reason.

The C++ language has a somewhat different design philosophy, but it's the same reason its std::array class-template has both a runtime-checked at member-function, and an unchecked operator[]. It would be against the design philosophy to force you to pay the runtime overhead for checks, but it gives you the option.

> which are prone to UB too if not done carefully

What kind of error do you have in mind here?

[0] https://stackoverflow.com/a/44820924/

Re: Tips for stable and portable software

#57
post #53

Earlier quoted context omitted.

What does "keenly aware" even mean? For example: any time I add or subtract two signed ints, undefined behavior can happen. Now what. Must I pepper the code with bounds checks (which are prone to UB too if not done carefully)? Anyway, any complicated thing that can be easily ignored, inevitably will be.

> What does "keenly aware" even mean? Keeping the threat of undefined behaviour in mind, and taking steps accordingly, rather than complacently ignoring it. C is a highly unsafe language, and the programmer shouldn't forget this. > any complicated thing that can be easily ignored, inevitably will be. The demonstrable inability of C programmers to write correct code is a strong argument against the widespread use of C…

For example checking for signed overflow must be done carefully:

https://stackoverflow.com/questions/3944505/detecting-signed...

"Design philosophy"...oh please! C was designed for transistor- and memory- scarce microcomputers. Nowadays there is defacto supercomputer in every phone and runtime bounds checks are cheap. Moreover, allowing CPU to know the size of memory chunk pointed to could enable optimization which would make the code actually faster (not even talking about security benefits). But you C programmers insist tooth an nail against that...

Re: Tips for stable and portable software

#58
post #32

Earlier quoted context omitted.

If it were so easy, there would be already specified a subset of C without undefined behavior and you could be able to automatically check your code against it.

My point was only that C programmers should be keenly aware of the pitfalls of undefined behaviour, rather than blithely ignoring it. I've been surprised by the sloppiness of some developers on this point. > a subset of C without undefined behavior There are various projects out there that let you produce C code guaranteed to be free of undefined behaviour, but they're not 'quick fix' solutions, so they're not widely…

I've been actively wondering about generating some efficient and portable C code, and for this project it wouldn't be super-complicated, but undefined behavior is the one thing that keeps me away. C++ and Rust and C# and many other languages all add wonderful things, with side effects on portability, clarity, learning curve, language stability, etc. - wonderful things that I don't always want in a twenty-year-stable system.

Anyway, thank you for these, I'm definitely going to look further here.

Re: Tips for stable and portable software

#59
post #57

Earlier quoted context omitted.

> What does "keenly aware" even mean? Keeping the threat of undefined behaviour in mind, and taking steps accordingly, rather than complacently ignoring it. C is a highly unsafe language, and the programmer shouldn't forget this. > any complicated thing that can be easily ignored, inevitably will be. The demonstrable inability of C programmers to write correct code is a strong argument against the widespread use of C…

For example checking for signed overflow must be done carefully: https://stackoverflow.com/questions/3944505/detecting-signed... "Design philosophy"...oh please! C was designed for transistor- and memory- scarce microcomputers. Nowadays there is defacto supercomputer in every phone and runtime bounds checks are cheap. Moreover, allowing CPU to know the size of memory chunk pointed to could enable optimization which w…

> For example checking for signed overflow must be done carefully:

Right, but we're talking about a simple bounds check. There should be no need for any arithmetic, just comparison.

> "Design philosophy"...oh please! C was designed for transistor- and memory- scarce microcomputers.

Right. Hence its design philosophy.

> Nowadays there is defacto supercomputer in every phone and runtime bounds checks are cheap.

Cheap, but perhaps not cheap enough to dismiss entirely. Bounds checking costs a few percent of performance [0], enough to put some people off in some domains such as in the kernel.

It's a pity C makes it difficult to automate just about any kind of check. Checking whether a pointer overruns a buffer that was returned by free, for instance, requires quite a bit of cleverness, as the system has to track the size of the allocated block.

You have to rely on optional compiler features, elaborate static analysis tools (often proprietary and expensive), and dynamic analysis tools like Valgrind. Ada on the other hand enables all sorts of runtime checks by default, but it's easy to switch them all off if you're sure.

> CPU to know the size of memory chunk pointed to could enable optimization which would make the code actually faster (not even talking about security benefits)

What kind of optimisation do you have in mind? Pre-caching?

> But you C programmers insist tooth an nail against that...

'Fat pointers' of this sort have been tried with the C language [1] but I can't see the committee adding them to the standard. Part of C's virtue is that it's extremely slow moving.

I'm not advocating continued widespread use of C though. I hope safe-but-fast languages like Rust do well. We all pay a price for the problems associated with C and, perhaps to a lesser extent, C++. For what it's worth I haven't written serious C or C++ code for a long time.

[0] https://doi.org/10.1145/1294325.1294343 (An old source admittedly)

[1] http://libcello.org/learn/a-fat-pointer-library

Post reply on HN