4 ms·
> Zero terminated strings are only used in C so every other language needs to do a copy to pass a string to C, even C++ (for string_view at least). This is one
by jstimpfle 5y ago
> Zero terminated strings are only used in C so every other language needs to do a copy to pass a string to C, even C++ (for string_view at least).
This is one of the most frequent complaints and I find it ridiculous. C has string literals that encode zero-terminated strings, but you don't have to rely on these zero terminators. There are a few awquard "string functions" in libc, most of which you should just ignore. Besides maybe the printf() family (which you probably wouldn't want to use for interoperability there isn't anything that requires zero-terminated strings.
This language gives you memory to work with, and it's up to you how you want to achieve your goal.
> A lack of fat pointers means that buffers need to pass their pointer and length separately, which is awkward, or use a custom struct which other languages will know nothing about.
It could be considered a security problem that sizes have to be passed separately, but what I consider awkward is actually the slices approach: In general the count field in a "slice" is redundant data - think about parallel arrays, where with slices you need to synchronize the count information across all slices. Passing separate length is the correct approach from a normalization standpoint.
> It has no standardized error handling mechanism to help with writing automatic wrappers on the other language endpoints.
Similar to the point about slices, not including any notion of "errors" is the correct way of doing it. In general you don't want errors in a system, only data that you handle in an appropriate way. Also, returning what people usually consider errors is often a bad idea, as such error information should better be stored in a persistent handle or context struct.
> It has no modules or namespacing.
Not a problem, since there are names that you can already use for disambiguation. Namespaces only add more ways to identify the same object, which leads to ambiguity and/or hard to read and hard to grep code. (Namespaces encourage the use of not-fully-qualified identifiers - actually this is the only thing they really add, and apart from some metaprogramming tricks you can play I don't think it's a great addition).
- unionpivo 5y ago> This is one of the most frequent complaints and I find it ridiculous. C has string literals that encode zero-terminated strings, but you don't have to rely on these zero terminators. Just a quick question. Is there somewhat up-to-date guide, list, book, whatever of good best practices for C, or maybe small libs for simple string handling, and common pitfalls (checking for over/underflows etc.)?
- jstimpfle 5y agoI don't know any in particular, but a few tips here. I would say rule 1 is to not use strings unless needed :-) My rule 2 would be to not use a library because that would probably add too much complexity (especially with regards to integrating memory allocation). There are different valid ways to represent strings, but a basic approach is of course to use arrays of bytes, i.e. pointer + length (struct String { char buffer; int length; }). An important consideration is the choice of allocation scheme for the byte buffer. I'd recommend to use statically allocated strings (like char buffer[32];*) where possible, and to look into memory arenas. Don't make "resizable" strings unless absolutely needed (with resizeable strings you might run into dangling reference problems more easily, and you will probably need a "capacity" field in addition to pointer + length). Most dynamically-sized use cases do not need resizing; you can conveniently cover them with a separate string builder (which can be implemented using a large statically allocated storage or using a resized-as-needed storage. Once the string is assembled, the string builder can create the final immutable string by allocating for example from a memory arena.
- sirwhinesalot 5y agoBecause a C "string" is a char* by convention, any time a library exposes string parameters that's what they use. This includes libraries not written in C! I can't even think of a counter-example. Using an own string type adds too much friction, particularly because it prevents automatic binding generation. With errors such a standard doesn't even exist so you get a mess. And yes, as a library developer you need to signal errors somehow, and whichever solution you use, the binding generator doesn't know about it. Rust has a nice error propagation mechanism which lets me attempt a set of operations but abort early when an error happens, but the binding generator can't use it. Namespaces are a similar problem, they force library devs to try and come up with unique but short prefixes. Had C had namespaces the "short" constraint wouldn't be a problem, pretty much eliminating the chance of collision. Am I right in assuming you're an embedded developer? Typically when I have this discussion with people the points that you make come from embedded devs. They're not reflective of the needs of library developers.