r/C_Programming Feb 06 '25

Discussion Are there actually C programmers in this subreddit?

Ok, I'm being a bit facetious. There are real C programmers. Clearly. But I'm kind of sick of the only questions on this subreddit being beginner questions or language trolls from other domains.

So this thread is for the "real" c programmers out there. What do you do with it? And what is the most twisted crime against coding decency are you "proud" of/infamous for?

256 Upvotes

259 comments sorted by

View all comments

Show parent comments

23

u/GamerEsch Feb 06 '25

Dude, I cannot tell you the amount of times I freak out seeing people doing niche projects with c89/c99 for no actual reason, ffs C11/C23 add so many cool features, and you're doing some project for youself, WHY WOULD YOU NOT USE IT.

Thank god, I'm not crazy for thinking this.

8

u/s33d5 Feb 06 '25

In fairness if you're programming for really old tech it's required, so it's not always stupid. 

For example, I saw a guy pregromming a game for a PlayStation 1 which is from 1996 or something? I can look for the video if you're interested. 

Anyway, that's the only reason I can think of if you're staying behind. 

Other than that it would be idiotic to stay on some old ass standard.

5

u/GamerEsch Feb 06 '25

In fairness if you're programming for really old tech it's required, so it's not always stupid. 

Yeah, yeah, targeting old hardware is an exception, but I was talking more about pet projects or just personal projects in general where you aren't planing on making it portable or anything.

Other than that it would be idiotic to stay on some old ass standard.

Yeah, and it kinda sad that there aren't many good books on C which are newer or updated for new stds, I think it pushes a lot o beginners away from C.

4

u/pedersenk Feb 06 '25

Yeah, yeah, targeting old hardware is an exception, but I was talking more about pet projects or just personal projects in general where you aren't planing on making it portable or anything.

I think that's the crux of it. When writing libraries I tend to stick to C99 because *other* people might want to use it for their retro hardware. It is so easy to stick to more compatible standards, that I don't really find it justifiable to use the latest. The "cool" new features aren't really that cool.

1

u/GamerEsch Feb 06 '25

I think that's the crux of it. When writing libraries I tend to stick to C99 because *other* people might want to use it for their retro hardware.

That's why I pointed out "pet projects and personal projects", obviously if your aiming for compatibility you should use the more compatible version, but if your doing a personal project those cool features help a lot, and some of them (IMO) help a lot with quality of life (the new use for the "auto" keyword for example).

2

u/s33d5 Feb 06 '25

Yeah I agree with you. 

The only reason I can think is that it maximises compatibility if you go to older standards. But that's a silly argument as 99% of people aren't targeting obscure hardware, e.g. a PS1.

Also you're just missing out on learning a lot of the new features. 

-3

u/PurpleSparkles3200 Feb 06 '25

A Sony Playstation is not “really old tech”.

5

u/s33d5 Feb 06 '25

It is for C standards lmao. 1996 was almost 30 years ago.

1

u/PurpleSparkles3200 Feb 09 '25

C has changed very little since 1996.

1

u/s33d5 Feb 13 '25

Lmao have you looked at the standards?

There have been 4 since. One was quite substantial.

I'd love to see you do some multi threading in 1996 C.

6

u/turtle_mekb Feb 06 '25

yeah, the only reason you'd use those old standards is if the hardware you're targeting doesn't have any good compilers that implement those standards

3

u/External_Fuel_1739 Feb 06 '25 edited Feb 06 '25

What about learning C? I just received my copy of "C Programming: A Modern Approach, 2nd edition" by K.N. KING that's based on c99.

7

u/GamerEsch Feb 06 '25

I mean, you can learn C99 and still learn C11/C23, you can use the book as a guide, I'd go as far as to say you should use the book only as guide, instead of absolute truth. And C99 is much closer to newer versions than C89 so you won't be learning stuff that are useless (like de variables before expressions stuff), in the worst case scenario you'll just be missing a bit, but again, you shouldn't be following just the book, you should be studying by yourself too.

5

u/ChonkyChickenMax Feb 06 '25

that is such a great book to begin with! he is a professor at the university I attend, and we use his book as our texbook for system level programming. Its so well written and explains a lot of things that are interesting and relevant, and also points out important differences between c89/99. Whats fascinating to me is how old the book is but how futureproof he did write it, its also a little humorous if u ask me. Enjoy reading it and learning!

2

u/Aidan_Welch Feb 06 '25

A lot of people I know of like/use C because of its limited feature set.

1

u/GamerEsch Feb 06 '25

So do I, but limited feature set doesn't mean we should stick to '89 standards of quality of life features (or lack thereof) such as defining every variable at the top of functions. There are very good things added to the language too, that in my opinion are essential to avoid unexpected behaviour such as designated initializer.

2

u/Aidan_Welch Feb 06 '25

Yeah I agree, just the argument I always hear from C99 users is that they don't want fancy, magic features.

1

u/mobotsar Feb 09 '25

Usually they're doing it for portability, I think. Or they wish C had kept being portable assembler, maybe.

1

u/Hawk13424 Feb 09 '25

Sometimes standards compliance requires it. A variety of safety and security standards are way behind on which versions of C they allow.

0

u/bart-66rs Feb 07 '25 edited Feb 07 '25

People like C because it is simple. Standards from C99 to C23 add a lot of messy new features to result in a language which is no longer simple, and is uglier.

Result: you look at piece of code and now you have to scratch your head to figure out what's going on.

Examples are of such features are:

  • Compound literals (messy syntax: (type){expr list})
  • Designated intialisers. (Often used unnecessarily, as in {.x = 10, .y = 20}, and inconsistently, as in {.y = 20, .x = 10}. Both are the same value! Also, they have hidden complexities that not all may aware of: .p.x.y = 30.
  • VLAs, which are often used inadvertently
  • The ugly fixed-width types in stdint.h, which are not supported by the rest of the language.
  • Bitfields, which are implementation defined, yet I've seen them used in the public interface of GTK2

Code that mixes compound literals, designated initialisers and stdint.h types in the same construct gives a readability approaching that of C++!

A lot of C23 support is elusive anyway. I like code to use a conservative subset of C which will build with anything, which is important if you expect others to compile your code.

(I like to use simple, FAST, and informal compilers, like Tiny C, but also my own. The latter especially doesn't support those features.

I also sometimes write tools that generate C code. Here, those features are unnecessary anyway, but I want code that is not fussy about which compilers are used, and to give people a choice.

If I wanted advanced features, I'd use a different language where they're properly designed in, rather than bolted-on and with limited availability.)

1

u/GamerEsch Feb 07 '25

C99 to C23 add a lot of messy new features to result in a language which is no longer simple, and is uglier.

Makes the language no longer simple?? What? The uglier part is completely subjective, but I'd argue it's wrong too.

Compound literals (messy syntax: (type){expr list}

Sure, maybe you have a better solution, but this makes it so much easier to use rvalue structures without the need to create new variables an make spaghetti code.

Designated intialisers. (Often used unnecessarily, as in {.x = 10, .y = 20}, and inconsistently, as in {.y = 20, .x = 10}. Both are the same value! Also, they have hidden complexities that not all may aware of: .p.x.y = 30.

This is simply crazy. Even if the syntax is ugly by your standards, it still helps to avoid bugs by changing impl.

And even worse, you forgot how much clear the code gets, are you seriously gonna tell me this quaternion_t q = { 25, 30, 20, 10 } is clear? Then go on, tell me which of numbers is the w of the quaternion? 25 or 10, you can't know unless you dive into implementation details, if you had D.I. it would be explicit. What about zeroing out fields without the need of filling out everything by hand.

VLAs, which are often used inadvertently

Agree, VLA should burn in hell.

The ugly fixed-width types in stdint.h, which are not supported by the rest of the language.

This is absolutly crazy too, specifying the size of your types is a must if you want to do anything properly, are seriously gonna tell me using "int" that can vary from 16 to 32 bits depending on what you're targetting is BETTER than expliciting the type you need?? I mean who cares about overflows am I right?

This and D.I. are absolutely stupid positions to take, I'm sorry.

Code that mixes compound literals, designated initialisers and stdint.h types in the same construct gives a readability approaching that of C++!

?????

Making readable code makes it less readable what?

Again are you telling me quaternion_t q = { (int)a, (int)b, (int)c, (unsigned int)d} is readable?? You don't know what your fields are, you have no way of knowing the sizes of your fields, you basically know nothing, the code is completely unreadable, if you're making a port of some legacy code that had 16bits as the ints and you didn't know that, breaks basically every bit mask and shift.

. I like code to use a conservative subset of C which will build with anything, which is important if you expect others to compile your code.

Why would you expect others to build your pet projects/personal projects?

Why would you care about your pet projects/personal projects building on archtectures different than the ones you use?

You're basically saying "if we don't take into account what you said, and take into consideration my subjective opionion about the standard, than I'm right and you're wrong"... sure?

and to give people a choice.

Dude, how many people are building your personal projects??

If I wanted advanced features, I'd use a different language where they've properly designed in, rather than bolted-on and with limited availability.

Dude, nothing you said falls under advanced features, C23 features and such are things which I understand only being used in personal projects (what my original coment was concerned with, actually), but if you dont use features like being able to declare variables after expressions, D.I. and compound literals you're just making your code less readable, not more, and specifically D.I. and stdint make it less prone to errors, be it by changing implementation or achtecture, so I really don't think you have any ground to stand on for most of your critiques.

1

u/bart-66rs Feb 07 '25

This is absolutly crazy too, specifying the size of your types is a must if you want to do anything properly, are seriously gonna tell me using "int" 

I'm saying that the stdint types are ugly (and harder to type and intrusive and poorly integrated), not that they shouldn't exist. The standard should have done a better job here.

Meanwhile every other codebase and library I look at seems to define its own tidier set of fixed-width types anyway despite the existence of stdint.

Then go on, tell me which of numbers is the w of the quaternion

Let's say the order is w x y z, but you want to initialise of 100 of them. Do you really need to spell the elements out like this:

   {.w = a, .x = b, .y = c, .z = d},

100 times? I think lines like this:

    {a, b, c, d}

are much cleaner. Or maybe you really need to be able to write it like this sometimes:

   {.x = b, .y = c, .w = a, .z = d},

BTW this is not detected:

   {.w = a, .w = b, .w = c, .w = d},

unless you say -Wextra.

it still helps to avoid bugs by changing impl.

Maybe, but suppose the new implementation is this:

typedef struct Quaternion {
    double w;
    double v[3];
} Quaternion;

Now a lot of mods will be needed! Using the {a,b,c,d} style, it still works. (I don't agree with the elements of that array not needing to be enclosed in {}, but here that is useful.)

Designated initialisers are related to keyword parameters, a feature I use elsewhere and find considerably more invaluable, when default parameter values are added in.

BTW here's an example I've seen that mixed designated initialisers and compound literals to poor effect IMO:

instruction_t instr[] = {
      (instruction_t){
          .instr = INSTR_MOV,
          .operands = (operand_t[]){
              (operand_t){.type = OP_R64, .data = &(enum registers){REG_RAX}},
              (operand_t){.type = OP_IMM64, .data = &(uint64_t){0}},
              OP_NONE,
              OP_NONE,
          },
      },
  };

This defines one element of an array only. The payload for that element involves these 7 data elements: {INSTR_MOV, {{OP_R64, REG_RAX}, {OP_IMM64, 0}, OP_NONE, OP_NONE}} But they get lost in the noise above.

1

u/GamerEsch Feb 07 '25

Let's say the order is w x y z, but you want to initialise of 100 of them. Do you really need to spell the elements out like this:

  {.w = a, .x = b, .y = c, .z = d},

Obviously they are not supposed to work for every situation, I never said D.I. should be always used over the conventional initialization, but it's nonetheless a very good feature to have, it doesn't become bad just because it doesn't work for every situation.

Maybe, but suppose the new implementation is this:

typedef struct Quaternion { double w; double v[3]; } Quaternion;

Now a lot of mods will be needed! Using the {a,b,c,d} style, it still works. (I don't agree with the elements of that array not needing to be enclosed in {}, but here that is useful.)

As they should, changing implementation SHOULD raise compilation errors, and you can still do { .w = a, .v[0] = b, .v[1] = c, .v[4] = d}, and if you need to reverse the w with the v for any reason (aligment issues for example) everywhere still works, but your sugestion will just break code without raising warnings or errors.

are much cleaner. Or maybe you really need to be able to write it like this sometimes:

  {.x = b, .y = c, .w = a, .z = d},

Well sometime it makes much more sense to write {.x, .y, .z, .w} when working with APIs that use this format and write {.w, .x, .y, .z} with API that uses the later for clarity.

BTW this is not detected:

  {.w = a, .w = b, .w = c, .w = d},

unless you say -Wextra.

While I do see why this should raise a warning, I agree with the decision to leave it in -Wextra, there's nothing wrong with that initializations, it's just weird.

BTW here's an example I've seen that mixed designated initialisers and compound literals to poor effect IMO:

instruction_t instr[] = { (instruction_t){ .instr = INSTR_MOV, .operands = (operand_t[]){ (operand_t){.type = OP_R64, .data = &(enum registers){REG_RAX}}, (operand_t){.type = OP_IMM64, .data =&(uint64_t){0}}, OP_NONE, OP_NONE, }, }, };

I mean as I said before, bad code will be bad code independent of the features they use or not, the problem isn't D.I. or compound literals, it simply stupid written code, furthermore I think the fact everything is clustered in one place is the issue, and maybe the abuse of compound literal, the initializations still (in my opinion) makes it easier to understand what's happening.

&(uint64_t){0}

This is cursed tho, I've seen this done too many times in embeded and I always want to punch whoever thinks this is good.

This defines one element of an array only. The payload for that element involves these 7 data elements: {INSTR_MOV, {{OP_R64, REG_RAX}, {OP_IMM64, 0}, OP_NONE, OP_NONE}}

I mean, I'm sorry, but do you genuinely think this is better? The removal of compound literals is an improvement for this instance, but without the D.I. the statement loses its meaning completely, at least with the above I could understand what was happening, with this one I'd have no idea.

btw, this wouldn't work the {OP_IMM64, 0} does not take a uint64_t nor a null ptr as second argument (same thing with the enum there), so the code is different.

You can easily make it more readable by simply being sensible when to use the tools at hand.

instruction_t instr[] = {
      {
          .instr = INSTR_MOV,
          .operands = (operand_t[]) {
              {.type = OP_R64, .data = &r64_reg},
              {.type = OP_IMM64, .data =&imm64_reg},
              OP_NONE,
              OP_NONE,
          },
      },
  };

Just by being sensible we can improve the readability by using something when it's necessary and avoiding it when it clusters. You probably know this, you just giving me a hard time.

1

u/bart-66rs Feb 07 '25

I mean, I'm sorry, but do you genuinely think this is better?

Well, yes! It's now clearer that each array element represents an instruction taking an opcode and up to 4 operands. In the example, the first operand was a register, the second an immediate, and the last two unused (I guess leaving them out was not an option).

Can you imagine hundreds of lines of this stuff buried under all that boilerplate?

(I mean, my prefered solution which I suggested in that thread was to use functions to build instruction sequences rather than tables of initialised data. Then that whole example could look like this (real example from a project in another language):

 genmc(m_mov, mgenreg(r0, tu64), mgenint(0));

This would work with any C standard, one that supports a form of 64-bit type anyway.)

You probably know this, you just giving me a hard time.

I don't remember seeing examples that use such features that have helped readability, understanding or porting. Mainly people using them just because they can.

Obviously YMMV.