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...
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.
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.
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.
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.
There isn't really a good reason not to allow the, is there? Just allow them.
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.
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.
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.
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.
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.
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.
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.
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);
}
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.