Remix.run Logo
Some C habits I employ for the modern day(unix.dog)
79 points by signa11 5 days ago | 24 comments
themafia a few seconds ago | parent | next [-]

> and I end up having all these typedefs in my projects

I avoid doing this now. It's more trouble than it's worth and it changes your code from a standard dialect of C into a custom one. Plus my eyes are old and they don't enjoy separating short identifiers.

> typedef struct { ... } String

I avoid doing this. Just use `struct string { ... };'. It makes it clear what you're handling. C23 finally gave us "auto", you shouldn't fret over typedefing everything anymore. I also prefer a "strbuf" type with an index and capacity so I can safely read and write to it with a derived "strview" which references into the buffer.

> returning results

The general method of returning structures larger than two machine words is fairly inefficient. Plus you're cutting yourself off from another C23 gem which was [[nodiscard]]. If you want the 'ok' value checked then you can _really_ specify that. Put everything else behind a pointer passed in an argument. The sum type logic works just as well there.

> I tend to avoid the string.h functions most of the time, only employing the mem family when I want to, well, mess with memory.

So you use strlen() a lot and don't have to deal with multibyte characters anywhere in your code. It's not much of a strategy.

WalterBright 2 hours ago | parent | prev | next [-]

> I’ve long been employing the length+data string struct. If there was one thing I could go back and time to change about the C language, it would be removal of the null-terminated string.

It's not necessary to go back in time. I proposed a way to do it in modern C - no existing code would break:

https://www.digitalmars.com/articles/C-biggest-mistake.html

It's simple, and easy to implement.

publicdebates 40 minutes ago | parent | next [-]

> the fatal error was not combining the array dimension with the array pointer; all it needs is a little new syntax a[...]; this won’t fix any existing code. Over time, the syntax a[] can be deprecated by convention and by compilers.

You're thinking in decades. C standard committee is slower than that. This could have worked in practice, but probably never will happen in practice. Maybe people should start considering a language like D[1] as an alternative, which seems to have the spirit of both C and Go, but with much more pragmatism than either.

[1] https://en.wikipedia.org/wiki/D_(programming_language)#Criti...

billforsternz 29 minutes ago | parent [-]

There is some irony in someone replying to the author of the D language suggesting that maybe the D language is the real solution he's looking for.

I_am_uncreative 11 minutes ago | parent [-]

A tale as old as time.

cogwheel 15 minutes ago | parent | prev [-]

https://web.archive.org/web/20260116161616/https://www.digit... for anyone here while we're swamping Walter's site

matheusmoreira 3 hours ago | parent | prev | next [-]

> In the absence of proper language support, “sum types” are just structs with discipline.

With enough compiler support they could be more than that. For example, I submitted a tagged union analysis feature request to gcc and clang, and someone generalized it into a guard builtin.

https://github.com/llvm/llvm-project/issues/74205

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=112840

GCC proved to be too complex for me to hack this in though. To this day I'm hoping someone better than me will implement it.

nine_k 3 hours ago | parent [-]

With proper discipline, one can even program a Turing machine directly. The problems are two: (1) Doing so is very slow and arduous, and (2) a chance of making a dangerous error is still quite high.

For instance, it appears that no amount of proper discipline, even in the best developers, allows to replace proper array support with a naked pointer to a memory area.

matheusmoreira 2 hours ago | parent | next [-]

The compiler's job is to program the turing machine for us. It should help as much as possible. For example, I really like using enums because compilers have extensive support for checking that all values have been handled in switch statements.

I don't like it when compilers start getting in the way though. We use C because we want to do raw things like point a structure at some memory area in order to access the data stored there. The compiler's job is to generate the expected code without screwing it up by "optimizing" it beyond recognition because of strict aliasing or some other nonsense.

convolvatron 2 hours ago | parent | prev [-]

you can certainly wrap the array with a structure which provides either bounds information to be checked with generic runtime functions, or specific function pointers (methods) to get and set.

you can paper over _alot_ of Cs faults. ultimately its not really worth it, but its not nearly as fragile and arduous as you make it out to be

keyle an hour ago | parent | prev | next [-]

That made me smile

     If I find myself needing a bunch of dynamic memory allocations and lifetime management, I will simply start using another language–usually rust or C#.
Now that is some C habit for the modern day... But huh, not C.
amiga386 33 minutes ago | parent | prev | next [-]

Fun fact: the background image is the "BallsMany" pattern included with MagicWB for the Amiga

(To confirm: download the LhA archive from https://aminet.net/package/util/wb/MagicWB21p then open the archive in 7-zip, extract Patterns/BallsMany then load into an ILBM viewer, e.g. https://www.retroreversing.com/ilbm )

tom_ an hour ago | parent | prev | next [-]

If you really insist on not having a distinction between "u8"/"i8" and "unsigned char"/"signed char", and you've gone to the trouble of refusing to accept CHAR_BIT!=8, I'm pretty sure it'd be safer to typedef unsigned char u8 and typedef signed char i8. uint8_t/int8_t are not necessarily character types (see 6.2.5.20 and 7.22.1.1) and there are ramifications (see, e.g., 6.2.6.1, 6.3.2.3, 6.5.1).

skywalqer 4 hours ago | parent | prev | next [-]

Nice post, but the flashy thing on the side is pretty distracting. I liked the tuples and maybes.

smnplk 4 hours ago | parent [-]

Not distracting at all, it feels nostalgic to me. Id rather have these flashy things than a million popups and registration forms following you around, which is basically the modern web. I hate it so much. This site is pure balsam for my soul.

Vedor 3 hours ago | parent [-]

Both nostalgic and distracting for me.

canpan 3 hours ago | parent | prev | next [-]

Regarding memory, I recently changed to try to not use dynamic memory, or if I need to, to do it once at startup. Often static memory on startup is sufficient.

Instead use the stack much more and have a limit on how much data the program can handle fixed on startup. It adds the need to think what happens if your system runs out of memory.

Like OP said, it's not a solution for all types of programs. But it makes for very stable software with known and easily tested error states. Also adds a bit of fun in figuring out how to do it.

vbezhenar 3 hours ago | parent | next [-]

In recent years I had to write some firmware code with C and that was exactly the approach I took. So far I never had need for any dynamic memory and I was surprised how far I can get without it.

thisoneisreal 2 hours ago | parent | prev [-]

I've been looking into Ada recently and it has cool safety mechanisms to encourage this same kind of thing. It even allows you to dynamically allocate on the stack for many cases.

JamesTRexx 3 hours ago | parent | prev | next [-]

Two things I thought while reading the post: Why not typedef BitInt types for stricter size and accidental promotion control when typedeffing for easier names anyway? I came across a post mentioning using regular arrays instead of strings to avoid the null terminatorand off-by-one pitfalls.

I still have a lot of conversion to do before I can try this in my hobby project, but these are interesting ideas.

jcalvinowens 2 hours ago | parent | prev | next [-]

  #if CHAR_BIT != 8
   #error "CHAR_BIT != 8"
  #endif
In modern C you can use static_assert to make this a bit nicer.

  static_assert(CHAR_BIT == 8, "CHAR_BIT is not 8");
...although it would be a bit of a shame IMHO to add that reflexively in code that doesn't necessarily require it.

https://en.cppreference.com/w/c/language/_Static_assert.html

gdjjg 43 minutes ago | parent [-]

Gtav

BigJono an hour ago | parent | prev | next [-]

I really dislike parsing not validating as general advice. IMO this is the true differentiator of type systems that most people should be familiar with instead of "dynamic vs static" or "strong vs weak".

Adding complexity to your type system and to the representation of types within your code has a cost in terms of mental overhead. It's become trendy to have this mental model where the cost of "type safety" is paid in keystrokes but pays for itself in reducing mental overhead for the developers. But in reality you're trading one kind of mental overhead for another, the cost you pay to implement it is extra.

It's like "what are all the ways I could use this wrong" vs "what are all the possibilities that exist". There's no difference in mental overhead between between having one tool you can use in 500 ways or 500 tools you can use in 1 way, either way you need to know 500 things, so the difference lies elsewhere. The effort and keystrokes that you use to add type safety can only ever increase the complexity of your project.

If you're going to pay for it, that complexity has to be worth it. Every single project should be making a conscious decision about this on day one. For the cost to be worth it, the rate of iteration has to be low enough and the cost of runtime bugs has to be high enough. Paying the cost is a no brainer on a banking system, spacecraft or low level library depended on by a million developers.

Where I think we've lost the plot is that NOT paying the cost should be a no brainer for stuff like front end web development and video games where there's basically zero cost in small bugs. Typescript is a huge fuck up on the front end, and C++ is a 30 year fuck up in the games industry. Javascript and C have problems and aren't the right languages for those respective jobs, but we completely missed the point of why they got popular and didn't learn anything from it, and we haven't created the right languages yet for either of those two fields.

Same concept and cost/benefit analysis applies to all forms of testing, and formal verification too.

sys_64738 3 hours ago | parent | prev [-]

#define BEGIN {

#define END }

/* scream! */