Skip to content

Instantly share code, notes, and snippets.

@Daniel-Abrecht
Created August 23, 2025 10:53
Show Gist options
  • Save Daniel-Abrecht/2174fb33475c420c008e608cafa33db2 to your computer and use it in GitHub Desktop.
Save Daniel-Abrecht/2174fb33475c420c008e608cafa33db2 to your computer and use it in GitHub Desktop.
Things in C which should be changed

Things in C which should be changed

There are some things that I'd consider design mistakes in C. Real problems which absolutely need to be fixed.

Now, C23 is coming out, but we get mostly unnecessary things like nullptr, instead of fixing real issues... (I have no intention of switching to C23, for me, C17 was the last real C so far). Well, at least typeof is real now...

Allow VMTs using later parameters in a function parameter list.

Currently, this works:

int f(int n, int x[n]);

But this does not

int f(int x[n], int n);

It'd be nice if the latter was allowed too.

Allow initializing Flexible Array Members (FAMs)

struct mystruct {
  int length;
  int data[];
};

struct mystruct x = {
  .length = 3,
  .data = { 1, 2, 3 }
};

The need for something like this can arise from time to time, and there is literally no sane way to do it. Although, some people do some UB hacks with unions or type punning to work around it. Other people rely on the compiler to implement it as an extension.

Only check the semantics of the matching generic branch

Consider an expression like this:

_Generic((X), int: (X)+1, struct a: (X).member)

It is correct syntactically, but as things are now, it will always fail semantic checks. If an int is passed, the branch trying to access a member on a struct is going to fail. And if an instance of struct a is passed, the branch expecting an integer will fail, because you cant add a number and a struct together. Ideally, if X is an integer, only the semantics for the expression (X)+1 should be checked, and if an instance of struct a is passed, only the semantics of (X).member should be evaluated. It failing because of anothr branch really isn't helpful.

It is possible to work around the problem:

_Generic((X),
  int: _Generic((X), int: (X), default: 0)+1,
  struct a: _Generic((X), struct a: (X), default: (struct a){0}).member
)

But it's error prone and a real pain in the ass, especially if you ever have to deal with multiple nested generics.

Allow passing an expression to _Alignof / alignof and offsetof

An expression can be passed to sizeof, but not to _Alignof or offsetof. So if you, for example, need the offset and size, of a member, for something, especially in a macro, it can get problematic. This is a bit less of a problem since C23 finally got typeof, but it's still worth changing.

Allow 0 length arrays (including VLAs)

There isn't really a good reason not to allow the, is there? Just allow them.

Allow empty structs

It barely ever happens, but there have been cases, where I needed an empty struct, but since that's not valid, I had to do work-arounds like putting a member in there and wasted space. Just define that empty structs are allowed, and have a size of 0.

Statement expressions

It's currently a GCC extension. I don't like the lack of an explicit return statement in them, but aside from that, they'd be really useful.

My pipe-dreams

Now to some features I'd really love to have. This isn't stuff that's really necessary, nor does it solve a real problem, but wouldn't it be nice to have?
Some people may not like these ideas, or may be of the opinion that some of it may be outside the current scope of C. Some of it is rather radical. It's unlikely any of this will ever get into the standard. But maybe I'll make my own c based language one day, then I'll add these things for sure.

Referencing earlier initializer results

Consider the FAM example from above. Wouldn't it be nice if you could write it like this:

struct mystruct x = {
  .data = { 1, 2, 3 },
  .length = sizeof(.data) / sizeof(.data[0])
};

As for the semantics, I propose to keep the initialization order undefined as it currently is, but the evaluation order should be in the order it's written down, and reading a member whose initializer expression has already been evaluated should simply return the result of the evaluation.

Also, some expressions, like sizeof(.length), are constant expressions which don't depend on the actual value of the field, bit only its type, so the evaluation order should not matter for those at all.

Namespaces

This is probably the only thing I miss from C++. Putting things in a namespace. Being able to access an enum in a struct using structname::myenum. That's actually really nice to have.

Weak symbols

Make them a proper keyword, define the semantics. This is useful for adding / removing some functionality solely based on if a file was linked into the binary or not, with no additional code generation or macro magic.

Padding and Bitfields should get a bit better defined

When it comes to bitfields, barely anything about them is defined. This should be changed. Define the order of bitfields, and to some extent, their representation. People often seek to use them for serialization / deserialisation, but as they are right now, they are not fit for that purpose. Also, maybe a packed keyword, similar to GCC packed attribute, should be defined. Currently, there is also no standardized algorithm for how the padding of members is calculated. That should be defined too.

Link-time arrays

Currently, I abuse sections to do this, or make a linked list using constructor functions, but it'd be useful if this was part of C proper. Basically, I want the ability to spread out a list over multiple compilation unit. The whole list will be known only after linking. This is useful for adding / removing some functionality solely based on if a file was linked into the binary or not, with no additional code generation or macro magic.

// animal.h
struct animal {
  const char* name;
  const char* sound;
};
extern weak struct animal animals[fragment];
// cat.c
#include <animal.h>

fragment(animals) cat = {
  "cat",
  "meow"
};
// dog.c
#include <animal.h>

fragment(animals) cat = {
  "dog",
  "woof"
};
main.c
#include <animal.h>

int main(){
  // Note: sizeof(animals) is not a constant expression here, because the size of the animals list is only known after linking.
  // But sizeof(animals[0]) is a constant expression
  for(int i=0; i<sizeof(animals)/sizeof(animals[0]); i++)
    printf("%s goes %s\n", animals[i]->name, animals[i]->sound);
}

Reflection

Wouldn't it be nice, if you could read the fields of a struct type? Imagine something like this:

// builtin types

struct _Meta {
  enum _MeatKind {
    STRUCT,
    UNION,
    INTEGER,
    POINTER,
    ARRAY,
    ATOMIC,
    FUNCTION
  } kind;
  size_t size;
  size_t alignment;
  const char* name;
  bool is_const;
  bool is_restrict;
  bool is_volatile;
};
struct _StructMeta {
  struct _Meta meta;
  size_t field_count;
  const struct _FieldMeta* field_list;
};
...
struct _FieldMeta {
  const char* name;
  size_t offset;
  const struct _Meta*const* type;
};
...

// main.c
void print_struct_fields(const struct _StructMeta* m_struct){
  printf("%s:\n", m_struct->meta.name);
  for(size_t i=0; i<m_struct.field_count; i++){
    const struct _FieldMeta* f = m_struct.field_list[i];
    printf("  type=%s name=%s offset=%zu size=%zu\n", f->type->name, f->name, f->offset, f->type->size);
  }
}

struct mystruct {
  int a;
  const char* b;
};

int main(void){
  print_struct_fields(_Reflect(struct mystruct));
}

This would become especially useful, if we'd get more constexpr stuff, including constexpr functions, and maybe some templating besides macros. Now, I wouldn't want anything so messy and ugly as what C++ has. Just stuff that looks like / is regular C code, but run at compile time. Maybe make a template look just like a regular function or something. I'll think about what that could look like later.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment