Hacker News new | ask | show | jobs
by dleslie 1616 days ago
I'd be fine with this, even totally on-board, if C weren't so awful with respect to text. You don't even have to worry too much about free()ing your malloc()s if you design around short-lived processes. But this is just asking for security concerns among the tangled web of string and input processing your bespoke C routines are likely to develop into.

Pair it with a better, more modern, and safer native-compiled language and get the same effect. Zig, Nim, Go, hell even Carp.

12 comments

> Pair it with a better, more modern, and safer native-compiled language and get the same effect. Zig, Nim, Go, hell even Carp.

I love how trollish it is not to talk about Rust in that context.

Maybe they didn’t want to bring forth the Rust stans
Your message looks like a perfect example of trolling to me.
This entire thread is a troll, or a demonstration of Poe's law.
If we'd just rewrite all of the things in Rust we could solve computer bugs forever, and world hunger too.
This having to re-write things is obviously Rust’s fatal flaw.

I cannot wait for the next great language, the one that brings all Rust’s advantages which is a pure superset so that it can still compile existing code. Surely something like that ought to end all these petty language rivalries forever!

This is what the Rust Evangelism Strike Force™ told me and by God, I believe them.
And end mortality!
Twice
Ah, the ol' double-free bug!
The last time someone promised to free the Men from the gift of Ilúvatar, it did not go well for them. A good reason never to use Rust!
BRHS does not sound as cool as BGHS. This is the only reason to exclude Rust and prefer Go. :p
I just wish there were better tools for navigating C codebases.

There’s been more than one time where I’m in some large auto tools based project trying to figure something out and there’s a call out to some dependencies I have no idea of.

Also many of the projects lack and sort of documentation or source code commenting. These aren’t someones pet project either. One of them was from a notable name in the open source community and the other one was a de-facto driver in a certain hardware space.

Using an IDE like VS/Clion/CDT/.. would already be of help.

Then there are tools like SourceGraph, CppDepend among others.

Use clang to generate a compilation db. Most IDEs support this format out of the box, otherwise via plugin or routed through youcompleteme.

https://clang.llvm.org/docs/JSONCompilationDatabase.html

Have you tried ctags for navigation? Or clang-lsp?
Try cscope.
It does seem sometimes that a lot of folks use C for philosophical rather than practical reasons.

That being said, I love seeing a push for simple stacks like this.

You don't even have to worry too much about free()ing your malloc()s

*gasp!* Such lack of symmetry... it disturbs something deep in my soul.

It's just a well known arena allocator pattern, implemented on OS level.
Is there a good string-manipulation C library?
The strbuf library that's part of git.git is a pleasure to work with. It's C-string compatible (just a char /size_t pair), guarantees that the "char " member is always NULL-delimited, but can also be used for binary data. It also guarantees that the "buf" member is never NULL: https://github.com/git/git/blob/v2.34.0/strbuf.h#L6-L70
This looks really well thought out - bookmarked.
Yes, SDS from Redis project.

https://github.com/antirez/sds

However the moment you call into other C libraries, they naturally only expect a char *.

I got tired of running into this problem and decided to simply eat the cost of using `char *` in my string library.
And that is why most such efforts eventually die.

WG14 could naturally work into something like SDS for strings and arrays, but of course that is out of their goals to ever do that.

> WG14 could naturally work into something like SDS for strings and arrays, but of course that is out of their goals to ever do that.

Maybe it is, but even if it were, sds strings are a poor choice. I used them extensively in a private project.

1. Typedef'ing `sds` to a pointer type. This leaves no indication to the reader of code that any `sds` typed variable needs an `sdsfree`. IOW, for every other standard type it is clear when the data object needs a `free`, `fclose`, etc. This is a big deal, it's difficult to change the typedef for sds due to the way it returns pointers.

2. Not compatible with current string functions, strike 1: storing binary data in the strings, like the nul character makes it silently lose data when used with current string functions that accept `const char *`. This is a very big deal!

3. Not compatible with current string functions, strike 2: an sds string is only compatible with current string functions that take a `const char *`. This isn't such a big deal (for example, it provides a replacement for `strtok` as the standard `sds` type won't work for `strtok`) but it's unnecessarily incompatible.

4. With the current way it's exposed to a caller, you cannot use `const sds` variables anywhere, which removes a lot of compiler-checking. Trying to use `const` on any sds variable is pointless as you get none of the error-checking.

While sds solves many problems with raw C strings, those problems can be solved by adding standard library functions that work with existing C strings. In addition, it adds a few more problems of its own.

"C strings" really aren't anything worth talking about. People take them way too seriously and then complain that they are "unsafe" or "hard to use". Look, C gives you memory to work with and the rest is up to you. Almost the only thing you want from C with regards to strings is string literals.

It should be obvious that most "string" APIs from libc like strcat, strcpy, but especially strtok are ridiculously bad and are only in the libc because of history. Don't use them.

Even strlen() is rarely a good idea to use, and you can (should?) replace strlen("abc") by sizeof "abc" - 1.

My point regarding WG14 wasn't to add SDS as they are, rather vocabulary types for strings and arrays in the same spirit as SDS.

When they exist as vocabulary types, the ecosystem can rely on their existence and slowly adopt their use, similarly to threads support introduction in C11, for example.

That's not really a problem if the only thing they need is direct access to a read-only view of the buffer (i.e. const char*) - then it's no different than C++ and std::string.
> Is there a good string-manipulation C library?

You will have to define "good". My string library[1][2] is "good" for me because:

1. It's compatible with all the usual string functions (doesn't define a new type `string_t` or similar, uses existing `char *`).

2. It does what I want: a) Works on multiple strings so repeated operations are easy, and b) Allocates as necessary so that the caller only has to free, and not calculate how much memory is needed beforehand.

The combination of the above means that many common string operations that I want to do in my programs are both easy to do and easy to visually inspect for correctness in the caller.

Others will say that this is not good, because it still uses and exposes `char *`.

[1] https://github.com/lelanthran/libds/blob/master/src/ds_str.h

[2] Currently the only bug I know of is the quadratic runtime in many of the functions. I intend to fix this at some point.

No? Asking for code nav and you get three answers. Asking for this and you get crickets. In the 90s I worked at a place where we embedded TCL into all the apps, and rolled our own templating systems. I had to do a little string stuff in C after few years of go, and it sucked. Ugg. buf[len] = ‘\0’;

Using go, I thought I was getting back to low level stuff but this C experience made me appreciate strings in Go. Web servers in C are crazy bad idea, especially if they are spitting out html. Lisp would be better. Node would be better. Go would be better.

> buf[len] = ‘\0’;

So why didn't you use one of the bazillion library functions or third party libraries that terminate strings for you?

I feel like most of the criticism is coming from people who punish themselves by rejecting library functions and then telling that strings are hard. Doh.

I like to see the terminating nul in there, just in case my math was off earlier. I had strdup and so on. I was just way more work than go. And I was writing a LD_PRELOAD that I didn’t want to drag in any extra dependencies other than libc. Trust me, it is faster and safer and more fun in Go than C.
Alright, yea, if you're working under constraints like that, C sucks more than it has to.

It just doesn't feel fair to criticize C without mentioning that your experience comes from working under such unusual constraints. "Strings are hard, C sucks" is quite different from "strings are hard, C sucks when you can't rely on libraries." Also feels unfair to say you get crickets when you ask for a lib but actually you weren't even willing to use one. (There are tons of string libraries for C, it's impossible to miss them if you look around.)

Even then, if you're stuck working with only libc and your own code, there's a very high chance you're doing something wrong if you're doing math on strings and terminating them manually. There's a fair selection of libc functions that do all the math for you and will always output a properly terminated string if your inputs are a buffer & valid size and strings.

Can't vouch for any in particular, but they do exist. https://github.com/oz123/awesome-c#string-manipulation
Considering that it's a stack that uses OpenBSD, my first thought would be Perl, although it's not a language that one could call “modern”, heh. It's included into the base system and has rich libraries for text processing, (Fast)?CGI, HTML, and all that.
If using C is a must, having static analysis as part of CI/CD pipeline and using libraries like SDS should be considered a requirement.

Otherwise, yes using anything safer, where lack of bounds checking isn't considered a feature is a much better option.

I wrote my first web app in 2000 using C/mysql. It was Insanely fast but very awkward to implement. I used C because it was (and still is) the only language I knew well.

At least if you are going to use C, you (should) know to be extremely paranoid about how you process anything received from the user. That doesn't remove the risk but at least you are focused on it.

This is the same in any language. You can cause security issues in other languages as well if you trust the user/attacker.
Same guy wrote a rad tool that will generate server code, schema, frontend using a markup language called ORT.

Will generate Rust and typescript if ya want.

One of the most rewarding parts of back-end web application development is re-writing the same database routines and same JSON export routines over and over. Then changing the requirements and starting over. It's what makes web application developers such well-balanced folks, right?

Unfortunately, in the flux of user requirements—each addition or modification of a table column changing select routines, insertions, validation, exporting, regression tests, and even (especially?) front-end JavaScript—we make mistakes. What BCHS tools beyond the usual can help in this perennial test of our patience?

;)

https://learnbchs.org/kwebapp.html

well there are still many large software written in C, e.g. nginx, lighttpd, even linux kernel.

I checked BCHS a few years back, the key piece is that it's Openbsd, if it's Linux it might have caught on, due to linux's popularity, good or bad. This could be useful for embedded device for example, but not so many embedded devices running OpenBSD, if any at all.

The C library used (https://github.com/kristapsdz/kcgi) is portable and working on linux as well. Putting this behind nginx as fastcgi seems very well doable.
there are also qdecoder or cgic or fcgi etc are totally linux ready for CGI, which by the way, all worked well. CGI is still perfect for a deeply embedded linux device.
Never heard of Carp, looks cool!
Why would you ever choose C anymore? The killer feature of C++ is “you don’t pay for what you don’t use”. There’s virtually no reason ever not to use C++.
>Why would you ever choose C anymore?

I can't speak to why you'd want to use C in a web stack, but I can weigh in in the more general sense:

A while ago I thought I'd try my hand at the Cryptopals challenges, and I figured, hey all the security guys know C (and python, but ugh) so I'll use this as an opportunity to really learn C. Prior to starting that project, I "knew" C, in the sense that I took CS036 which was taught in that small subset of C++ that might as well be C.

So I jumped in and it felt really liberating, at first. You want to compare chars? It's just a number, use >= and <= ! Cast it back and forth to different types! Implement base64 by just using a character array of the encoding alphabet and just walk through it like an array and do magic casts! No stupid boilerplate like namespaces or static class main{}!

Then by about the 2nd set where you have to start attacking AES ECB I realized I was spending more time debugging my mallocs/frees and my fucking off-by-one errors than I was spending on actually learning crypto. I stuck with it until I think part way through the third set but by that point I couldn't take it any more.

So I bailed out of C and never looked back. I can see how a certain type of programmer (who is more practiced with malloc/fastidious with their array indices and loop bounds than I am) can really enjoy C for a certain type of work. But I can actually say now, hand on heart, that I know C; and I don't like it.

Brilliant summary of the path that a lot of C-programmers have taken.
Isn’t “C++ is too complex for me” a decent reason?
It totally is, as long as you don't use C instead. There are plenty of good, less complex languages than C++ out there: Java is quite close and way, way less complex, for example.

But C is non of them. Pointers are more complex in C than in C++ (pointer provenance). Casts are more complicated in C than in C++, as C++ named casts are less powerful and therefore give you less opportunity to shoot yourself in the foot. Strings (I'd guess quite important part of web programming) are a minefield in C, and they are so much better in C++. Resource management, memory ownership, object lifetimes. All very hairy in C++, all a good reason to not use C++. but also reasons to not use C. Types are a mess in C++. C is even weaker typed, making it worse.

If you like procedural programming and dislike the complexities of C++, i suggest you write C++ without member functions, without inheritance and compiler disabled exceptions. It will feel similar, but it will be better. I suggest using a c++ compiler to write C with enum classes, std::span, either a typed std::span wrapper for malloc or std::vector<std::char8_t>, std::string, std::string_view,using, std::variant, std::unique_ptr, std::fmt, std::optional, templates, std::vector for your types, in that order. And don't use C style var-args and C style enums, C style unions. You can have very C-Like code, that is way less complex than C.

> There are plenty of good, less complex languages than C++ out there […] But C is non of them.

To this day I cannot understand how anybody can claim that. C++ is so ridiculously complex that it’s not a superset of C mostly because of added keywords and rules, some of them implicit.

> I suggest using a c++ compiler to write C with enum classes, std::span, either a typed std::span wrapper for malloc or std::vector<std::char8_t>, std::string, std::string_view,using, std::variant, std::unique_ptr, std::fmt, std::optional, templates, std::vector for your types, in that order.

How on earth do you write that and expect me to believe it’s simpler than C? You’ve just listed a number of options on how to implement an *enum* of all things. Each one will have different pros and cons, and I’m supposed to weight them before coding?

> Pointers are more complex in C than in C++

That’s impossible from you own statement. Pointers in C can be more dangerous because the compiler won’t stop you from doing crazy shit, I’d buy that, but complex?

C++ feels like the Mikrotik version of C, there’s just too many options and buttons and switches, and I won’t use half of this in my life! I guess some people find it interesting but I’m just trying to get my job done.

You can think of simplicity in various ways - some subset of which are valid. For example, you could say that using std::unique_ptr is simpler than manual memory management, because it automates much of the process, making it less likely that a developer will make a mistake. Or you could say that the 1000s of lines of C++ std code that you will pull in represents an increase of complexity, because the mechanics are hidden under layers of abstraction.

I'm open to both views, and most importantly to trade-off one against the other according to other constraints and context. For me, using C++ allows me to do that, because it's (mostly) a superset of C. Acknowledging, of course, that this brings a trade-off of its own, because decisions must continually made (and enforced) about what std/external libraries to use and when.

I’m willing to buy that abstractions reduce complexity, but to do that they can’t be leaky abstractions. They should “just work”, and it should be easy to choose among them.

If you need to consider what’s underneath, how is that a reduction in complexity?

C++ abstractions are the opposite. You need to ponder the several options, normally tagged by their implementation details.

> Mikrotik version of C

?

Reference to the Latvian network kit manufacturer?

Yes, have you used any of their devices? They’re amazing in what they let you do, but their UI is a wall of settings.

Doing something as simple as adding an access point will take you a full day if you aren’t familiar with it.

>added keywords and rules, some of them implicit.

Yes, and I agree that that's a problem. But to a much lager degree it removes implicity from C. If you don't write member functions and ust inheritance (and I'm serious about this) I don't see any added implicity.

>How on earth do you write that and expect me to believe it’s simpler than C? Y

Because It is. Let me walk you thu:

- enum classes: work just like enums, minus the implicit conversions.

- std::span: incredibly simple: A struct with a pointer and a size. All wrapped up in a struct to signify indent: You don't own this. This is very simple and removes implicit assumptions from c apis. Putting data that belongs together in one struct is not alien to C. AFAIK it's considered good design.

- either a typed std::span wrapper for malloc: This is just a malloc that replaces void * for a proper type. Again, adding simplicity and removing implitity. And a facility to bounds check in way that is harder to mix up. Again, placing data that belongs together in a struct. However, if you do this, you give up the conventions that spans are not owing. That's because you might consider a std::vector<T> (same but also can to realloc), or just write a owing_span template that does the same. The name is just very convenient documentation. By the way, the suggest classes so far can reasonably written by yourself in a few lines of code.

-std::string : This one is the first to be non trivial to implement by your self, but dont be picky, just use the one from the standard. It very easy to use. And C Strings just suck.

-std::string_view: This is a struct{const char*; size_t;}, plus a few member functions, without any magic. While I suggest you don't write you own member functions, using this is just fine. And significantly harder to make mistakes with than with C.

-std::variant: This is just a union. But with most footguns removed. If you don't use unions in C, dont juse this.

-std::unique_ptr: This is this first bit of code that is somewhat magic, as it cleans up after itself. Just always correctly placed free()s. It also signifies ownership. I see how this is somewhat controversial, but for me this a massive simplification. But see, this list was ordered, and this was towards the end.

- std::fmt: Enjoy how C-Style var args don't have any type safety? Love the good old printf-exploits? Then this is not for you. Otherwise it's pythons easy and loved string interpolation.

-std::optional, templates, std::vector: This is definitifly getting more c++ is, but not complicated. Std::optional is a struct with a bool and one additional member, templates are way to do generic programming in a way easier way than preprocessor macros. But yes, may to much C++ fore some peoples liking. That's why these are last.

>That’s impossible from you own statement.

C does some pretty IMHO insane stuff called pointer provenance. AFAIK C++ doesn't. Also named casts. Harder to remove const by accident.

>You’ve just listed a number of options on how to implement an enum of all things.

Uhm.. What?

>but I’m just trying to get my job done.

If there is one pragmatic language out there, It's c++ (or maybe perl). In fact I'd say that why is so complex compared to more academic languages like scheme, haskell, prolog and pascal.

Yeah, I'm just going to keep writing C.

Thanks.

Object Pascal/Free Pascal then is the obvious choice, it is The better C.
I do use Lazarus/FreePascal for multiplatform GUI apps. It is also very easy to write servers in. Unfortunately performance is worse vs C/C++ and frankly "batteries" included in C++ are way more powerful and simpler to use so I choose C++ for backends. As for all that FUD around C++ - I am not C++ expert at all but I find modern C++ to be very productive, fast for backend development and pretty safe unless one's goal is to purposely shoot themselves in the foot.
What batteries are included in C++? There's the STL, but most of the rest is a mess to the point it's actively avoided in favor of alternatives (iostreams, locale).

Now if you do something like C++ with Qt, that's a very different proposition - but that applies both to batteries being included, and to the overall coding style.

The last time I looked at c++ (mid-1990's) it generated hugely bloated binaries and was generally slower at runtime than plain C. Is that still true today?

Is there any language (other than assembly) that is faster at runtime than C today?

> The killer feature of C++ is “you don’t pay for what you don’t use”

In what way C makes you pay for what you don't use?