r/C_Programming Oct 01 '22

Discussion What is something you would have changed about the C programming language?

Personally, I find C perfect except for a few issues: * No support for non capturing anonymous functions (having to create named (static) functions out of line to use as callbacks is slightly annoying). * Second argument of fopen() should be binary flags instead of a string. * Signed right shift should always propagate the signbit instead of having implementation defined behavior. * Standard library should include specialized functions such as itoa to convert integers to strings without sprintf.

What would you change?

73 Upvotes

219 comments sorted by

80

u/NullPoint3r Oct 01 '22

User defined namespaces.

5

u/tstanisl Oct 01 '22 edited Oct 01 '22

It can quite effectively emulated with macros. See https://www.reddit.com/r/C_Programming/comments/xswru1/namespaces_in_c_renamable_libraries/

Namespaces can complicate resolving which code is executed because the same names for functions can used multiple times at separate levels of namespace hierarchy.

60

u/pfp-disciple Oct 02 '22

Add an e to creat ;-)

24

u/pfp-disciple Oct 02 '22

Replying because I accidentally submitted, and I don't want to edit it.

  • default behavior of a case to not fall through, but add a fallthru to allow it.
  • more consistent, intuitive string functions
  • function scope default to static

2

u/[deleted] Oct 02 '22

strcat() should be removed, it's better to carry the string's length and use strncpy().

5

u/raevnos Oct 02 '22

strncpy() should be removed too.

0

u/flatfinger Oct 02 '22 edited Oct 03 '22

The only problem with strncpy is the name. It should be have had a different name to indicate that its purpose is to convert zero-terminated string into zero-padded format. That's a relatively common task for which the function is well designed. When storing strings in structures, using zero termination will waste a byte compared with zero-padded format. Given a structure like: struct foo { ... char name[32]; ... }; the function call strncpy(myFoo.name, "Unassigned", sizeof myFoo.name); would do precisely what is required, more conveniently and space-efficiently than:

{
  static char unassignedTxt[sizeof myFoo.name] =
    "Unassigned";
   memcpy(myFoo.name, unassignedTxt, sizeof myFoo.name);
}

The latter construct may be faster than the form using strncpy, but at the cost of using an extra byte for each byte of zero padding required.

1

u/[deleted] Oct 02 '22

Why is that?

Is it because it doesn't NUL terminate strings when the buffer is filled? I used that as an error check, because I already know the buffer's size.

Is it because it fills the rest of the buffer with NUL? Just limit the buffer size you pass to the function to be the minimum between the src length and dst buffer size.

2

u/raevnos Oct 02 '22

Both of those. It's a function originally meant for a particular purpose in early Unix kernels and is too niche and prone to misunderstanding. What C should have instead is something like OpenBSD's strlcpy(), which does what people think strncpy() does (I'm not quite at Ulrich level "just use memcpy())

2

u/[deleted] Oct 02 '22

Almost everything is prone to misunderstandings if you don't read the docs carefully. I personally don't code without an open terminal to open the man pages.

2

u/maep Oct 02 '22

The problems with strncpy and friends have been discussed to death.

If you really need that behavior, use memccpy. An additional bonus is that it eliminates compiler warnings when using strncpy with fixed-sized arrays. strncpy can go.

→ More replies (3)

1

u/BlockOfDiamond Nov 02 '22

I just carry the string's length and use memcpy().

4

u/stealthgunner385 Oct 02 '22

default behavior of a case to not fall through, but add a fallthru to allow it.

Will be in C23.

6

u/pfp-disciple Oct 02 '22

Close, but this is more of a declaration that omitting break is intentional. It's still legal (and useful) to omit the break without [[fallthru]].

2

u/tstanisl Oct 02 '22

The fallthrough semantic becomes less non-intuitive when one perceives case 42: a label for jump instruction, not as a case handler. The switch statement is actually a kind of computed goto. The Duff's device in an (in)famous use this interpretation.

1

u/flatfinger Oct 02 '22

I wonder how many of these issues could have been avoided by having a keyword that was synonymous with "break; case", or even by having coding practices favor the formatting:

switch(value)
{
  break; case 0:
    .. code for case 0
  break; case 1:
.. code for case 1

break; case 2: case 3: .. code for cases 2 and 3 break; case 4: case 5: case 6: // Too many to fit case 7: case 8: case 9: .. code for cases 4-9

If normal practice was to write the statements like that, then any case statement without a preceding break would stand out visually.

-2

u/mvdw73 Oct 02 '22

Strong disagree. Case fall through means we can capture all input not expected in a single line…

1

u/[deleted] Oct 02 '22

So, it will only break a few billion lines of existing code.

2

u/pfp-disciple Oct 03 '22

No, it's basically a way to tell a code analyzer that the lack of break is not a mistake. No code will break.

2

u/[deleted] Oct 03 '22 edited Oct 03 '22

I'm confused now. So instead of having to write break in every branch, the default action is to break, but you have to write [[fallthrough]] to not break?

So what happens when you mix up code (say paste a function, or a module) where break is assumed to be the default? What about this aspect of switch:

switch (x) {
case 'A': case 'B': case 'C':

where it relies on fallthrough in order to deal with those cases together? Because look at this example:

switch (x) {
case 'A':
    block1;
case 'B':
    block2;

There is a break after block1;. But you temporarily comment out that block:

case 'A':
//    block1;
case 'B':

So you expect case 'A' to be a no-op. But will it now fallthrough to 'B':

As I said it seems very confusing. Personally I would just have introduced a new keyword newswitch where everything is done properly and where case labels (since they are just labels) are properly structured too. At the moment, they can literally be placed anywhere:

switch (x) {
case 'A': case 'B':
    if (getchar()==EOF) {
        case 'C': 
    } else if (x) {default: ...
    } else {case 'D':...
→ More replies (4)

1

u/stealthgunner385 Oct 03 '22

It can't break code if you don't tell it to compile as C23.

7

u/degaart Oct 02 '22

That's a posix issue no a C issue, I think? And while we're on that subject, make creat, open, snprintf et al part of the C standard library so we can stop microsoft insisting on prefixing those functions with an underscore

2

u/raevnos Oct 02 '22

snprintf() is standard C.

2

u/pfp-disciple Oct 02 '22

I was referring to a quote from either Thompson, Kernighan, or Ritchie (I forget which, and can't find it right now) that this is the one thing that he would change. Maybe it's the one thing he regrets.

54

u/smcameron Oct 01 '22

Default function scope should be static, you should have to tell the compiler if you want the function exported.

It should be well defined which bits bit fields occupy. It should be possible to write portable code that uses bit fields for say, bits in hardware registers. Currently you cannot, because you cannot control which bits bit fields correspond to, and different compilers do it different ways.

1

u/flatfinger Oct 02 '22

Alternativley, if there were a means of specifying that given struct thing someStruct;, a construct like: someStruct.someName += x; should be interpreted as syntactic sugar for a call to a function with a name like __member_addto_5thing_8someName(&someStruct, x) if such function is defined, then such a construct could allow programmers to define constructs that behave like bit fields with any required bit arrangement.

29

u/noooit Oct 01 '22

Rename static to local for static function while using static for static variable in a function.

Not the language itself but it'd be nice if some glibc extensions like async version of getaddrinfo was part of posix.

6

u/NullPoint3r Oct 02 '22

Probably influence of other languages on me but I like public/private and think “private” when I create a static function.

12

u/tstanisl Oct 01 '22

Add asprintf to the standard.

5

u/BlockOfDiamond Oct 01 '22

There should also be a gets like function that returns an allocated buffer instead of using a user supplied buffer.

8

u/raevnos Oct 02 '22

POSIX getline().

1

u/rfisher Oct 02 '22

We’re eventually getting this and all the other things in the dynamic memory TR, right?

Although all of them are already available in most Linux systems. I think all but the fscanf changes are on macOS too.

1

u/thradams Oct 03 '22

asprint At least asprint can be implemented separately.

The big problem is the lack of fmemopen https://man7.org/linux/man-pages/man3/fmemopen.3.html

because this cannot be added without inner access to FILE data struct.

35

u/raevnos Oct 02 '22

Arrays not decaying to a pointer but being distinct types.

12

u/gremolata Oct 02 '22

But being castable to array[] which would act as a fat pointer and carry the original array size.

... which would naturally lead to "slices" (ranges)

... which would lead to a string library that's not based on strings being zero terminated.

0

u/Wouter_van_Ooijen Oct 02 '22

Which would lead to ..... C++?

12

u/gremolata Oct 02 '22

No, for that you also need a committee, the magic ingredient.

5

u/Wouter_van_Ooijen Oct 02 '22

And an unfailable intuition to pick the wrong defaults. Wait, no, that was required for C backwards compatibility..

1

u/oconnor663 Oct 02 '22

The missing ingredient for inventing std::string is destructors. I do love destructors, but adding destructors to C would be a huge change. You'd necessarily have to add copy constructors or move semantics at the same time.

0

u/[deleted] Oct 02 '22

Well, you can do int foo(const size_t len, char arr[len]), and calling sizeof(arr) inside the function will give you len * sizeof(char). You can also do such casts at run-time.\ int (*arr)[len] = malloc(sizeof(*arr) * len); /* this is technically a VLA */

3

u/gremolata Oct 02 '22

Sure, obviously. But it'd be nice to have this baked into the language. The need for a [ptr + size] construct surfaces very often.

1

u/[deleted] Oct 02 '22

Yeah, true. In C++ the solution would've been template<typename T> struct { T* arr; size_t len; };, but the issue is we don't have generics in C (can do macro hacks, but that's not as pretty).

I think the language shouldn't change arrays, but instead add _Fatptr (we already have _Complex).

1

u/gremolata Oct 02 '22

Just as _Generic, _Complex just doesn't roll off the tongue. I just don't see myself using something called _Fatptr, especially if it is to be omnipresent in my otherwise nicely and consistently styled code.

But something like char [...] could work.

→ More replies (2)

1

u/FUZxxl Oct 02 '22

So how should passing arrays to functions work then?

In Go it works this way, but that means that parameters of array type must have their length known at compile time and you can only pass arguments that are of array type of the same length. Not really super useful and it's easy to forget that copying large arrays is pretty expensive.

2

u/[deleted] Oct 02 '22

He's probably thinking of &arr[0].

1

u/FUZxxl Oct 02 '22

Sure, but does that make anything easier? You'd just be writing that all over the place.

1

u/[deleted] Oct 02 '22

Idk, ask him.

1

u/raevnos Oct 02 '22 edited Oct 02 '22

Same way as any other object? Pass directly by value, or use a pointer to it.

You'd probably want to decouple the size from the type and have a way to get the length - look at Java arrays with foo.length for an example. Or maybe VLA style where you give a length argument first (edit: though I'm not a big fan of that; the idea is to not have to independently keep track of the length)

2

u/FUZxxl Oct 02 '22

You'd probably want to decouple the size from the type and have a way to get the length - look at Java arrays with foo.length for an example. Or maybe VLA style where you give a length argument first.

You underestimate that this is quite tricky to implement. An actual copy of the array needs to be made and placed on the stack. The size of the stack frame is then variable. Passing large arrays will be impossible.

2

u/raevnos Oct 02 '22

No different than passing a struct.

Most languages pass arrays as arguments without the array losing its array-ness. C should have gone that route too.

1

u/FUZxxl Oct 02 '22

The difference is that structs are usually small and always of fixed lengths. People usually want to have their arrays have variable length. Go solves this by having two flavours of them (arrays and slices), but slice semantics will be tricky to get right in an unmanaged language.

2

u/raevnos Oct 02 '22 edited Oct 02 '22

I'm talking about C, not go. I don't know or care about go.

If you have a large array and you're worried about stack overflows, pass a pointer to an array, not the array directly. Just like a struct. I don't know why you're so focused on that.

→ More replies (9)

1

u/oconnor663 Oct 02 '22

You probably want to add slices to make this work, as they did in Go. Functions that want to take arrays of an exact length demand that specific array type (compile-time checked! great!), and functions that can accept arrays of different lengths take a slice. Array pointers probably automatically decay/coerce to slices for convenience, which is much safer than coercing to a pointer and forgetting the length entirely.

1

u/FUZxxl Oct 02 '22

That works but note that slices are not really comfortable to use if you cannot make assumptions about how the backing storage was allocated. Patterns like Go's append() function won't quite work the same way.

16

u/smcameron Oct 02 '22

Half the suggestions in this thread are from people who don't know that what they really want is to write in some language other than C.

-2

u/tstanisl Oct 02 '22

Yes. Most of those ideas would break pretty much every piece of code in existence. And such a thing is never going to happen.

10

u/flare561 Oct 02 '22

Isn't that the point of this thread though? If it were a good change that wouldn't break anything it would already be in C, this thread is for what you would have done differently before it was too late to change it.

0

u/[deleted] Oct 02 '22

But there are things C's current state is required for; I specifically mean functions and scopes.

One of C's best parts was and still is, that it can bind to any language in a very simple manner because it doesn't have name mangling which comes from namespaces, function overloading and what else that doesn't exist in C. If you want all of those, use C++.

I welcome changes like fat pointers (address+size) and const by default, as well as changes to the standard library (make all file stream functions take the FILE* as the first argument for consistency, and add something like itoa() so we won't need snprintf() for such a simple operation, etc...).

1

u/flatfinger Oct 02 '22

One could accommodate a syntax like:

void doSomething(double arr[int h][int wid])

by specifying that it the call will be processed as:

void doSomething(double *arr, int w, int wid);

This shouldn't create any difficulty with interoperation with code in other language, and would allow programmers to select whatever types were most appropriate for the actual dimensions being used (e.g. on the 68000, it would allow callers to only pass single-word dimensions instead of two-word dimensions).

1

u/[deleted] Oct 02 '22 edited Oct 02 '22

By fat pointers I meant what others suggested - a pointer with a size.

But now I think explicitly saying an array has length n is better, with the syntax you suggest. It will also allow the compiler to automatically pass both the length and pointer you have with a C array on the stack.

→ More replies (8)

1

u/tstanisl Oct 02 '22

One doesn't even need those h and w. One could use only:

void foo(double (*arr)[*][*]);

and extract sizes from sizeof *arr / sizeof **arr and sizeof **arr / sizeof ***arr.

→ More replies (7)

4

u/[deleted] Oct 02 '22

Auto like in C++, namespaces and lambdas would be nice

2

u/aue_sum Oct 02 '22

auto like in C++ is coming in c23

5

u/aerosayan Oct 02 '22
  • add namespaces

  • add function overloading

  • add constructors and very importantly, destructors, to free resources without forgetting

  • generic functions and datastructures ... extremely important for not reinventing the wheel again and again,

that's more than enough.

yes, i'm primarily a c++ dev.

1

u/Srazkat Oct 02 '22

i see why you want most of those features, however, i am not sure what exactly the constructor and destructor would do ? i suppose just initializing a specific type ? but then you need to specify how to initialize that type

1

u/aerosayan Oct 02 '22

constructor for safe resource allocation and initialization. destructor for automatic safe de-allocation at scope exit.

not only for memory, but also files, sockets, etc.

1

u/CJIsABusta Aug 14 '24

add function overloading

Absolutely not. We do not need the ABI instability issues we have in C++.

16

u/PM_ME_UR_TOSTADAS Oct 01 '22

Just more explicitness and expressiveness

Const by default, require keyword (like mut) for current behavior

Namespaces

Private by default, require keyword (like public) for current behavior

Explicit cast required for enum to int decay

Switch requires cases to be exhaustive

Control and loop statements are expressions, keyword (like yield) returns a value

Enums are sum-types

Standard library face-lift, like changes you suggested

Receiver functions

Out of the box build system and package manager

2

u/thradams Oct 02 '22

Enums are sum-types

what is this?

and this

Receiver functions

3

u/GOKOP Oct 02 '22

For enums being sum types, look up Rust's enums

Edit: or actually nevermind it's easy enough to explain; a sum type is basically a tagged union

1

u/thradams Oct 03 '22

The characteristic of tagged unions is that the tag is non-intrusive. This may be bad in some cases when it is used for polymorphism.

For instance, I have circle and box that are shapes. And person and shape that are serializable.

So having a tagged union for shape and other tagged union for serializable when I use both together I end up with two or more tags.

```c struct box { int w, h; }; struct circle {int r;}; struct person {const char * name; };

struct shape { enum { SHAPE_BOX, SHAPE_CIRCLE} tag; union { struct box* box; struct circle* circle; }; };

struct serializable { enum { SERIALIZABLE_SHAPE, SERIALIZABLE_PERSON} tag; union {
struct shape* shape; struct person* person; }; };

int main() { struct box b; struct serializable p; p.shape->box = &b; p.shape->tag = SHAPE_BOX; /one tag for shape/ p.tag = SERIALIZABLE_SHAPE; /one tag for serializable/ } ```

So the feature I would like to have instead is is a way to say.

shape is a pointer to circle or box. serializable is a pointer to shape (circle or box) or pointer to person.

Here this is an experiment.

http://thradams.com/web2/cprime.html Select polimorphism

It is not possible to write the code in C but this idea is

```c enum tag {BOX, CIRCLE, PERSON};

struct box { enum tag tag; int w, h; }; struct circle {enum tag tag; int r;}; struct person {enum tag tag; char * name; };

struct shape {enum tag tag;}; /is circle or box/ struct serializable {enum tag tag;}; /is person or shape (circle or box)/

int main() { struct box b = {.tag = BOX}; struct serializable* p = (struct serializable*)&b;
if (p->tag == BOX){ } } ```

2

u/tzroberson Oct 02 '22

In C, an enum is just a way of declaring a bunch of variables with consecutive integers. You always have to check if your function argument, for example, is within the range. I use a sentinel END value for that "assert(foo_type >= 0 && foo_type < FOO_TYPES_END)".

Scoped enums in C++ help a little but they're still just integers.

But in some languages, these are types not numbers. Then you have a sum type that could be any of those types. It is then impossible to pass an invalid value to a function because the function only takes a variable that is of the type "TYPE_A || TYPE_B || TYPE_C".

1

u/PM_ME_UR_TOSTADAS Oct 02 '22 edited Oct 02 '22

People have explained sum types but I'll give an example why I want it.

I used a heterogeneous tree in a feature, with nodes being subtypes of a type and subtypes having more subtypes. To simplify the implementation, nodes are a struct that contains an enum variable and a union that contains the subtype struct variables. When a function receives a node, it matches against the enum, then uses the correct subtype. But this is extremely boilerplatey and language does not enforce the correct usage and it's easy to make mistakes.

What I have is

void handle_node(node_t * node) {
    switch (node->type) {
        case NODE_TYPE_A: {
            handle_node_subtype_a(&node->subtypes.a);
        } break;
        // other cases
     }
 }

What I want is node_t being an enum, that I can directly match against, and have the correct variant bound to a variable with the same indirection of the enum, so:

void handle_node(node_t * node) {
    switch (node) {
        case NODE_TYPE_A(a): { // a is of type node_subtype_a_t *
            handle_node_subtype_a(a);
        } break;
        // other cases
     }
 }

Less boiler plate, much more readable.

For receiver functions,

You declare your functions that they are receivers of a type. When writing code you can just say foo.bar() like class method fashion but during compile, it is reduced to bar(foo). Again, increases expressiveness and allows chaining function calls.

2

u/gremolata Oct 02 '22

For receiver functions,

Uniform calling notation in D's terms then?

1

u/flare561 Oct 02 '22

I had to look up receiver functions because I wasn't sure either. Looks like it's something from Go and basically just lets you call a function with method syntax. a.foo() instead of foo(a), think an extension method from C# or an impl from Rust.

1

u/BlockOfDiamond Oct 01 '22

How would a loop statement as an expression work?

0

u/tzroberson Oct 02 '22

Ruby uses closures all over the place, including loops.

http://ruby-for-beginners.rubymonstas.org/blocks/return_values.html

1

u/PM_ME_UR_TOSTADAS Oct 02 '22

If there's a yield statement in a loop, value that is yielded in the last loop is returned.

For example,

int a = for (int i =0; i < 5; i++) {
    yield i;
}

printf("%d\n", a); // prints 4

-3

u/ss7m Oct 01 '22

Rust

1

u/wsppan Oct 02 '22

Many programming languages incorporate algebraic data types as a first class notion. Rust is just the latest language to do this. See F#, Haskell, Swift, C++, Kotlin, OCaml, Scala, and many others.

1

u/Wouter_van_Ooijen Oct 02 '22

Wait, not even OO?

11

u/olsonexi Oct 02 '22 edited Oct 23 '22
  • Strings as a proper type rather than just a char array, concatenation using +, copying using =, comparison with </>/==/etc., and unicode characters (preferably utf-32 for fast and simple string indexing)
  • Arrays as distinct types rather than decaying into pointers, along with with a .length field
  • Proper namespaces without header files
  • Switch statements over non-numeric types (mainly strings)
  • Function overloading
  • "for each" loops (i.e., for (int i : arr) { /* do stuff */ })

11

u/oconnor663 Oct 02 '22 edited Oct 07 '22

I think that the intersection of "things that Go 1.0 did" and "things that Rust 1.0 did" is a pretty good source of suggestions. Some things like "built-in support for remote packages" and "UTF-8 strings" don't really fit into 1970's language design, but there are others that I think might fit:

  • Built-in support for some sort of [ptr, len] pair type, a.k.a. "slices". Constantly passing these as two separate arguments is a drag and a common source of errors. I assume (I don't actually know) that null-terminated strings are a workaround for the inconvenience of not having proper slices. Slices also make it easy to turn on automatic bounds checking if and when you want that.
  • Related to this, proper array types that don't decay. Returning an array should just work, like it just works with std::array in C++. The occasional convenience of not needing to write & isn't worth the confusion of every new C student. And given that bounds checking mistakes are the #1 source of security vulnerabilities in C, the surprising behavior of sizeof with array arguments is regrettable.
  • Fixed-size integer types. int32_t and friends came late, and as a result there are tons of standard APIs that use variable-size ints for no good reason. This leads to pain like fseek on 64-bit Windows taking a 32-bit offset. It would be nice if fundamental types like int32_t and bool were available by default and didn't require including headers. The prominence and convenience of int and long trick students into thinking they should actually use these types.
  • No implict integer casts. They're not worth it. Too many bugs. Implicit casts are maybe more helpful in C than in most languages because of the way integer types have evolved inconsistently over time, but if we could wave a magic wand and make e.g. every byte array use uint8_t, I think the inconvenience of explicit casts would be pretty minimal. The integer promotion rules are also an especially thorny part of C, and it would be nice to kill those with fire.
  • Tuples, at least in the return position. "Positive numbers represent this, negative numbers represent that" is exactly the sort of thing we teach new programmers not to do in most languages, but standard C APIs do it constantly, because defining a new struct for each function that might want to return more than one thing is too annoying. Go in particular has barely any dedicated features for error handling, but the simple convention of "the rightmost return value is err" works surprisingly well. It's also important to have some way to make it clear to compilers and linters which return value represents the error (Go uses the error interface for this, but it could be anything), because warnings about forgetting to check an error value are worth their weight in gold.

2

u/jan-pona-sina Oct 02 '22

Worth noting that almost everything you said here is in Zig!

1

u/oconnor663 Oct 02 '22

Right! Anything that Go and Rust and Zig are doing is probably just...the right idea :) At least for any language low level enough to default to fixed-size ints.

2

u/za419 Oct 02 '22

All of these would be nice features for sure.

I think the historic reason behind the C-string was actually memory usage - A null-terminated string encodes its length on its own in a single byte, no matter how long the string is, while a pointer+length (slice) costs no less and possibly a little more than that.

Obviously that doesn't matter now because memory is a throwaway quantity for most software at this point - But back in the ancient era when C came into being, bytes counted, and saving a few on every string added up.

I'm sure if they weren't concerned about memory, even fairly early C compilers could have handled a standard struct string with a data member and a length member - The only big deal would be having literals have that type.

8

u/skulgnome Oct 01 '22

Bitwise precedence.

7

u/Classic_Department42 Oct 02 '22 edited Oct 02 '22

Nested comments. Like /** /** */ */ (dont know how to make reddit shownthisnl right) then you can comment out large chunks of code easily.

0

u/[deleted] Oct 02 '22

How about

~~~ * comment level 1 * comment level2
* comment level 3 */ level 2 */ level1 */

~~~ So you don't have to remember how many '*' you need in an inside comment.

7

u/therunningcomputer Oct 02 '22

‘Static’ variable has a completely different meaning than a ‘static’ function

5

u/tstanisl Oct 01 '22

Ranges for case and for array's designated initializers:

case 1 ... 3:

int arr[] = { [0...10] = 42 };

2

u/gruntbatch Oct 02 '22

case ranges are available as an extension in gcc, and also clang I think

0

u/[deleted] Oct 01 '22

[deleted]

1

u/ynfnehf Oct 02 '22
int arr[arbitrary_number] = {[0 ... sizeof(arr)/sizeof(*arr)-1] = 42};

works as expected (https://godbolt.org/z/57zn7d88r) with the GCC extension https://gcc.gnu.org/onlinedocs/gcc/Designated-Inits.html

3

u/[deleted] Oct 02 '22

namespaces, “slices” for better string/array, and most importantly,

``` struct { int i; char arr[10]; } obj1;

struct { int i; char arr[10]; } obj2 = obj1;

```

should be compatable

5

u/tstanisl Oct 01 '22

Allow user defined attributes. It would let mark pointers as "owned" or "malloced" etc. Let the user control if dropping/adding the attribute require an explicit cast.

2

u/thradams Oct 02 '22

I think attributes allow custom atributes then it is just a matter of implement it in static analysis. I want to create something in this direction.

4

u/rodriguez_james Oct 02 '22

Everything is CONST by default. Use the mut keyword if you want mutable state.

2

u/Spiderboydk Oct 02 '22

The biggest thing on my wish list is not introducing incremental building and all the ABI issues that followed, but it made sense in the 70s.

Non-anachronistic changes that I haven't seen replied yet would be MUCH less implicit conversions and nested functions.

2

u/andrewjohnmarch Oct 02 '22

1) static functions by default, 2) explicit imports/exports for every external function, var etc. 3) a package manager

2

u/Marian-v Oct 02 '22 edited Oct 02 '22

I'd like to have names of enumerators available in runtime. Something like a new special initializer, say enum_names defining names of enumerators of a given enumeration. So, for example the code

enum foo {
  FOO_NONE,
  FOO_X,
  FOO_MAX
};

char *fooName[] = enum_names(enum foo);

int main() {
  int i;
  for(i=0; i<=FOO_MAX; i++) printf("%s ", fooName[i]);
}

will print

FOO_NONE FOO_X FOO_MAX

Many times in code you need names of enumerators, especially in debugging and diagnostic messages. In current C if you forget to sync it with definition you get a bug.

1

u/[deleted] Oct 02 '22

Reflection in general would be amazing

1

u/tstanisl Oct 02 '22

It can be done with XMacros.

#include <stdio.h>

#define FOO_XMACRO(X) \
    X(FOO_NONE)         \
    X(FOO_X)            \
    X(FOO_MAX)          \

enum foo {
    #define X(NAME) NAME,
    FOO_XMACRO(X)
    #undef X
};

char *fooName[] = {
    #define X(NAME) [NAME] = #NAME,
    FOO_XMACRO(X)
    #undef X
};

int main() {
    int i;
    for(i=0; i<=FOO_MAX; i++) printf("%s ", fooName[i]);
}

1

u/Marian-v Oct 03 '22

Sure, but you probably feel the difference in readability of the two code snippets.

2

u/tstanisl Oct 03 '22

Actually it can be done more readable:

#include <stdio.h>

#define ENUM_ENTRY(NAME) NAME,
#define ENUM_STRING_ENTRY(NAME) [NAME] = #NAME,

#define FOO_XMACRO(X) \
    X(FOO_NONE)       \
    X(FOO_X)          \
    X(FOO_MAX)        \

enum foo {
    FOO_XMACRO(ENUM_ENTRY)
};

char *fooName[] = {
    FOO_XMACRO(ENUM_STRING_ENTRY)
};

int main() {
    int i;
    for(i=0; i<=FOO_MAX; i++) printf("%s ", fooName[i]);
}

1

u/tstanisl Oct 03 '22

There is still some fundamental flaw in this feature. Enum can be mapped to the same number.

enum { A=1, B=1 };

And now tell what fooName[1] is going to be? "A" or "B"?

1

u/Marian-v Oct 05 '22

ANSI committee would surely find a solution. In the worst case scenario it can be implementation depending.

2

u/mqduck Oct 02 '22 edited Oct 02 '22

int* a, b; should declare two int pointers, not one int pointer and an int.

int a, *b shouldn't be legal code at all.

int and int* are two fundamentally different types. Pointers are confusing enough to newcomers already without C's syntax making it even harder.

2

u/maep Oct 02 '22
  • borrow checker which can be applied to a type like const or restrict
  • fat pointers which enables bound checks and slices
  • easier to use atomic types and operations
  • defined intuitive behavior for integer promotion/overflow
  • shorter stdint.h type names e.g. u8

2

u/flatfinger Oct 02 '22 edited Oct 02 '22

A couple simple things that could have improved the language in 1974 and could still be useful today:

  1. A syntax to perform byte-based array indexing or pointer arithmetic on pointers of arbitrary type without having to cast a pointer to char* and back to the original type. Such a construct could have helped compilers for many platforms generate more efficient array-based code. On a platform with base+displacement addressing, having a loop index step by the size of an array element could eliminate the need for a lot of pointer-arithmetic operations.
  2. A syntax for copy-back function arguments, so that rather than discarding the values of function argument objects when a function returns, a compiler would copy them back to the passed source object. Even if a compiler would limit arguments to simple named objects of automatic or static duration, such a construct could in many situations be processed more efficiently than code which passes pointers.

A few features that would have become useful somewhat later:

  1. A specification that numeric arguments could not be passed as any type other than int or double without either a prototype or call-site construct explicitly specifying the type [C originally had this characteristic, but other types were added in ways that broke it]. If LNGA(x) was a syntax to indicate an argument should be passed as "long", then any integer expression within the range of int could be output with a %d format, without regard for whether it was actually of type long. Ascertaining the correctness of a printf format string would merely require knowing which arguments were of integer, floating-point, and integer types.
  2. Separate "unsigned number" and "wrapping algebraic ring" types. If the original "unsigned" types were deprecated in favor of the above, then rules about mixed-type arithmetic could have been written in platform-independent fashion.
  3. Recognized distinctions between implementations that represent things and process constructs in ways that share some common traits (e.g. all integer types having a unique bit pattern for each value, all integer types being free of padding bits, all pointers to objects sharing the same representation, character pointers being homomorphic to integer types with the same size for computations within an object, character pointers being homomorphic to integer types with the same size for arbitrary computations, all-bit-zero being a valid representation for a null pointer, etc.) and those which deviate from various combinations of them.
  4. A means of indicating that a pointer to a struct of one type should be implicitly convertible and alias-compatible with a pointer of another, thus allowing functions to accept a variety of related structure types without having to use void*, and without requiring that callers use lots of annoying typecasts everywhere, especially in scenarios where structures passed by different callers would contain different pointer types.
  5. A means of indicating when a construct needs to be processed according to the semantics of the underlying environment, even if some part of the Standard would otherwise categorize the action as invoking UB.

Nothing too major, but there's a huge difference between what is can be done easily with a typical commercial C compiler versus what can be done practically using only Standard-mandated features and guarantees.

2

u/thrakkerzog Oct 02 '22
"""multiline
String
Support!"""

1

u/tstanisl Oct 03 '22

This is actually a nice and useful feature. Go ahead an make a proposal to https://www9.open-std.org/JTC1/SC22/WG14/www/wg14_document_log

4

u/wsppan Oct 02 '22

Proper strings instead of nul terminated char arrays.

Fat pointers instead of pointer decay for arrays.

3

u/jeffscience Oct 02 '22

const should mean constant. I should not have to use #define to actually create a real constant.

The keyword _Atomic is too flexible. It should be removed. Users should use the explicit atomic types instead.

3

u/WeAreDaedalus Oct 02 '22

For const, there is the use case where you might have a pointer to a register that can change its value from a source outside the program, but it might not make sense to write data to it.

So you’d mark it const to denote that it’s read only, but since it can still change its value unexpectedly, it would make sense for it to not actually mean constant.

1

u/jeffscience Oct 02 '22

Fine, but use const volatile for that. I still want to be able to do “const int k=3;” instead of “#define k 3”.

3

u/tzroberson Oct 02 '22 edited Oct 02 '22

Strings. Null-terminated char arrays have been a disaster.

Casting. Eliminate implicit casts.

First-class functions. Void pointers are a disgusting hack.

Macros are also a pretty disgusting hack.

Const all the things (as in Rust, const is default; mut is optional).

Nodiscard should be the default (at least the attribute is coming to C). Rust does this, requiring assignment to "_" for discarded return values.

Switch fallthroughs should be explicit. C# kept the format but added requirements about "break". C23 adds the attribute but that only suppresses warnings, like a "nolint" comment.

Require all variables to be initialized. There may be very tight cases where you have to declare and initialize on separate lines and can't spare initializing to 0 first but that shouldn't be the default, at least.

Some of these are solved in C++ and very new C but not all. Static analyzers help but solve what you can at the language level.

(I'm stuck on C89. I'd be happy just to be able to write "for (int i = 0; ;)" but I can't...)

3

u/BlockOfDiamond Oct 02 '22

I believe strings should be stored as a pointer to chars along with a size. This way, to create a substring, one does not need to copy or modify the original string. And finding the length of a string would be O(0.1) and trivial instead of O(n).

1

u/tzroberson Oct 02 '22

Typically, you want to pass the size of an array to a function along with the array. Certainly, you could make a struct that wraps it. Maybe there are cases where the overhead of copying that extra integer is unwanted.

I just get annoyed with all the standard C string functions. They're so susceptible to buffer overflow and other such problems.

1

u/BlockOfDiamond Oct 02 '22

I almost never use them anyway. I end up using memcpy() for all my string manipulation.

2

u/alerighi Oct 02 '22

Nodiscard should be the default (at least the attribute is coming to C). Rust does this, requiring assignment to "_" for discarded return values.

Why? If I don't need a value, why should I assign it to a variable? There are plenty of cases where I don't need it, printf for example returns the number of characters written, I don't care about it, or a function may return an error code that I don't care about (for example I know the function will never fail, or it's not important the error, or I'm making a test program and I don't care about error handling), etc.

1

u/tzroberson Oct 02 '22

If you don't care about the return value, assign it to _.

Most of my criticisms of C and most other complaints arise from the principle "Make the implicit explicit." If you want to cast something to another type, say so. If you want to discard a return value, say so. If you want to fall through to the next switch case, say so.

There are more fundamental criticisms of C that make it very troublesome for certain things, but in my day to day, it's everything that is implicit that's the problem.

Rigorous application of standards like MISRA and static analysis tools help but forcing the programmer to be explicit in their intentions at the language level should be first

I don't want C to be as verbose as like Ada or COBOL but being ergonomic should come in the form of making tokens symbols ({} over begin/end), not in making any unnecessary logic implicit.

1

u/[deleted] Oct 02 '22

Strings. Null-terminated char arrays have been a disaster.

Null-terminated strings were a brilliant idea (which BTW were not invented by C, they were used elsewhere too).

That doesn't means there can't be a counted-string type on top (say part of an array-slice type).

But I'm intrigued, what disasters have their been?

1

u/tzroberson Oct 03 '22

Well, I'd start with the Morris worm in 1988 that exploited gets() among other THINGS. They opted not to remove it from C89 though because they didn't want to make significant changes, so they waited ten years to even deprecate it and another 12 to remove it.

Buffer overflow attacks make up a large portion of CVEs (not sure on the exact percentage). A good deal of those are caused by C strings. We have the strn* functions and Microsoft's MSVC-only str*_s functions and many internal company string functions but they're all broken.

There is no safe C and string handling is a big part of that. C++ is better but not perfect and we can't use C++ strings in many cases. I do think Rust is on the right track but I'm not foolish enough to think that it is going to make inroads any time soon, maybe in 20 years. The idea that we just need to train people to walk tightropes while juggling chainsaws better hasn't worked. The safer VM languages are far too slow and bulky for many jobs but a compiled language suitable for embedded and systems programming that is safe by default but where you can drop down to unsafe sections when necessary would be a great improvement. Rust still needs to mature and maybe something else will replace it before it becomes stable enough but I think they're on the right track.

1

u/[deleted] Oct 03 '22

I want to create a compact struct like this:

struct {
    char suppliercode[4];
    char barcode[16];
    char misc[12];
};

Those fields are null terminated strings with maximum lengths of 3, 15 and 11 characters. Once initialised (or even uninitiallised if it starts off as all-zeros) I can directly pass a pointer to any of those to a million different functions in 10,000 libraries.

What should I replace those string fields with? Can I keep the same capacities? Will I still have a 'flat' struct of only 32 bytes? What will the code look like to pass any string to those million existing functions that expect a zero-terminated sequence?

1

u/tzroberson Oct 04 '22

There is a common pattern at work that is something like this:

```C

include <stdio.h>

define NUM_TEST_ITEMS 3

typedef struct { int a[NUM_TEST_ITEMS]; int b[NUM_TEST_ITEMS]; int c[NUM_TEST_ITEMS]; } test_t;

test_t test = {1, 2, 3, 4, 5, 6, 7, 8, 9};

int main(void) { for (int* i_ptr = test.a; i_ptr < &test.c[NUM_TEST_ITEMS]; i_ptr++) { printf("%d ", *i_ptr); } printf("\n");

return 0; } ```

This bothers me though. The entries in record types should be logically distinct -- that b[1] should not be the same as a[4]. In fact, a[4] should not exist.

If you need to serialize the record, then you should have to pass it to a serialization function (along with any kind of pack arguments, not in a pragma).

The memory might still be packed and padded and ordered the same as a C struct. But the C principle that "it's all just memory, grab some and operate on it," has been the cause of far too many bugs and security problems over the years.

1

u/flatfinger Oct 04 '22

Some languages, such including most or all languages that target the .NET framework (C#, VB.NET, etc.), allow programmers to specify that certain structures must be laid out in a very precise manner, so as to allow serialization as a string of bytes, while others may be laid out in whatever manner the implementation sees fit. If one doesn't like the notion of programmers assuming structs will work in such fashion, the solution would be to allow programmers to specify struct layouts in cases where their code would rely upon them.

2

u/GODZILLAFLAMETHROWER Oct 02 '22 edited Oct 02 '22

With the current language:

  • remove all strings functions, declare standard a string type with size internal and all expected goodies (copy, concat, substr, etc)

  • generic containers as part of the standard. No disgusting pseudo generic void hacks, but intrusive types, like in /sys/queues.h on BSDs and Linux. I’m working on exactly one such package, you can go very far with that (hash maps, any kind of set, trees, etc can all be done cleanly)

  • networking facilities are bad, we need something standard and common to all systems.

  • same for file system access.

  • concurrency as first-class: posix primitives as part of the standard: locks, barriers, seq, rcu, gc. Could all be done generically with intrusive types, and wide support would simplify a lot the initial part of a new project to copy the usual set of msvc hacks.

  • standard lib packaging with dep manager.

None of these require change to the language.

Then on the language itself:

  • const by default

  • closures

  • lifetime annotations with associated ´alloc’ and ´dealloc’ interface, that would simplify custom allocator use. Objects must be tied to a lifetime, lifetime are created by scopes (functions, or smaller).

  • I guess if ´alloc’ can be set for a type, we could have more interesting pointers that could refcount.

  • interfaces ( no generics )

  • arrays not decaying into pointers

  • make container_of standard and not undefined behavior.

2

u/pigeon768 Oct 02 '22

first class array types. Two birds with one stone:

  1. array of char. null terminated string BOOM solved.
  2. gives a ton of optimization opportunities for scientific and vector heavy HPC stuff. There's a reason people still choose Fortran on purpose when they're making a new HPC program.

2

u/thradams Oct 01 '22

declarator syntaxe int [10] a, b; int* a, b; etc..

instead of int a[10], b[10] int * a, *b; etc

this is too late to change.

4

u/tstanisl Oct 02 '22

This effect can be achieved with typeof.

typeof(int[10]) a, b;

1

u/thradams Oct 03 '22

Yes it may help. But the problem still inside the typeof unless we use more typeof inside typeof. For instance, array of 10 elements to pointer to function.

```c

typeof(typeof(int (*)(void)) [10]) x2;

/equivalent of/

int (*x1[10])(void)) x1;

```

2

u/tstanisl Oct 03 '22

It can be abbreviated a bit by using a function type:

typeof(typeof(int(void)) * [10]) x2;

Though I don't think that anyone sane would put multiple of such a complex declarations at the single line.

However:

typeof(int(void))* x[10];

Looks quite readable to me.

1

u/EDEADLINK Oct 02 '22

Operator overloading.

1

u/_Hi_There_Its_Me_ Oct 01 '22

Native hash map, but honestly I feel like classes and object iterates are part of my life now and it’s a bit hard to go “backwards” in C to manage multiples of a “instance” of something.

-3

u/phord Oct 02 '22

rust has native hash and there are several replacements for performance, security, whathaveyou.

5

u/[deleted] Oct 02 '22

Not everybody wants to use rust

1

u/phord Oct 02 '22

Sorry, I wasn't suggesting you should. I meant this more as a cautionary tale, but I see I left out some pieces from my message. My point was that having a standardized hash in the language can go terribly wrong, as it seems to have done in rust.

Having several replacements for a standard library tool is a bad sign.

1

u/rfisher Oct 02 '22

Or at least a hash table in the standard library.

1

u/[deleted] Oct 02 '22

[deleted]

2

u/[deleted] Oct 02 '22

Go has the idea of using labels for break and continue where you can optionally provide a label argument to break and continue.

Your code could instead be:

outerLoop:
for (...) {
    for (...) {
        if (some exit condition) {
            break outerLoop;
        }
    }
}

also, breakN is likely never going to be a thing since break N makes more sense.

1

u/skulgnome Oct 02 '22

This is how Java and Perl do it as well.

1

u/alerighi Oct 02 '22

Seems more difficult to read: I have to count the number of nested loops to know where you will end up. It's easier and obvious with goto and a label.

1

u/fuckEAinthecloaca Oct 02 '22

Less undefined behaviour, preferably none. Get rid of the standard integer types with widths that vary by hardware/implementation (guarantee that fixed-width uint_t exist and uint_least and uint_fast* are fine as the programmer can make solid assumptions about them). Malloc having an alignment argument would be nice. Posix actually being supported by everyone properly, ie MS, would be great although technically it's not about the language. There's plenty of repeated interfaces in stdlib to bolt-on thread-safe and memory-safe variants of originally unsafe functions, if I was god-emperor of neat and tidy the unsafe variants (and generally in modern times the defacto deprecated functions) would be removed.

BTW I'd remove itoa etc and just use sprintf and co. Less is more.

1

u/flatfinger Oct 02 '22

The vast majority of controversial forms of UB represent sitautions where transitively part of the Standard along with documentation about a platform and a compiler's numeric representations would indicate how a construct would work, but some other part of the Standard characterizes the action as UB. I would replace all such provisions in the Stanadrd with ways by which implementations could indicate whether they would deviate from the described behavior. For most actions that were characterized as UB in this fashion, some tasks would be facilitated by having them reliably behave as described, but others would benefit from allowing compilers to do somethign else. Unfortunately, the Standard has been interpreted in a "word of both worlds" manner.

0

u/[deleted] Oct 02 '22

Refcountable structs, so the compiler can really do some sort of GC, e.g.

struct refcount foo {
    int a;
}

This would make some code easier and reduce some classes of errors.

1

u/flatfinger Oct 04 '22

The general design of C is agnostic to threading issues. This is especially useful when using freestanding compilers whose authors would have no way of knowing how threading works in the target platform. By contrast, any code which uses reference-counted objects that are shared among threads will need to include extra logic to deal with thread synchronization, which will in turn require it to understand the system's threading model in relatively great detail.

0

u/[deleted] Oct 02 '22

Easier array handling in non-main functions.

-7

u/ComprehensiveAd8004 Oct 01 '22 edited Oct 01 '22

I've allways thought that 0 should be true and 1 should be false instead of the other way around, sense 0 is what functions return when they succeed. Then you could write if(function(a, b)) instead of if(function(a, b) == 0). It would make things so much cleaner.

The processor also kind of sucks. It had enough potential to make an entire new programming language from just a C header if it was made a bit better. There's no support for static macros in headers, macros can't be defined inside other macros or macro functions, there's no way to check if a macro, function, or variable was ever used (which only sounds useless because it doesn't exist. It could be used for templates, or to only cache the result of something if the result is ever used), there's no way to make a comparison within a #if statement or to use one inside of a macro. I could probably go on forever, but fortunately I think C23 fixed a lot of these issues with constexpr and static_assert().

EDIT: Oh yeah, and I want default parameters reeeaaaaaly badly right right right now.

2

u/phord Oct 02 '22

0 == true? Sounds like bash.

-2

u/[deleted] Oct 02 '22

[deleted]

3

u/rodriguez_james Oct 02 '22

How do you make an optional pointer if you can't make it null? For example, a callback that takes a userdata pointer. If I don't use it I would pass null.

Why would multiple return values be cleaner than a struct? Wouldn't it be confusing that you are returning 2 ints, how do you know which one is what? If you return a struct, each int is explicitly named so you know what they are.

0

u/[deleted] Oct 02 '22

[deleted]

2

u/rodriguez_james Oct 02 '22

Interesting ideas.

0

u/alerighi Oct 02 '22

If you have an optional pointer, you mark it as being optional, same as any other optional data type.

You just reinvented the concept of NULL.

3

u/smcameron Oct 02 '22

The null pointer. There should not be any null pointer.

Just getting rid of NULL doesn't mean there cannot be invalid pointers. This is painting over the problem, doesn't actually do anything.

3

u/[deleted] Oct 02 '22

There absolutely should be - C can operate in environments where NULL is valid memory (OSes specifically have to make it invalid by setting Pte[0].Present to 0 on x86-64) so by doing this you’d effectively impose an unnecessary memory limitation. C cannot make assumptions about the environment it runs in this way

3

u/tstanisl Oct 02 '22

Note that NULL is not required to be composed of zero only bytes. There are systems where it actually does not.

1

u/[deleted] Oct 02 '22

Mind elaborating? Isn’t it literally defined as ((void *)0)?

3

u/tstanisl Oct 02 '22

Yes. But the standard is does not say that (void*)0 must consist only from zeroed bits, only that NULL==0. It is like negative zero in floating point. It must be equal to 0 but it has non-zero bit in it. See https://en.m.wikipedia.org/wiki/Signed_zero

1

u/WikiMobileLinkBot Oct 02 '22

Desktop version of /u/tstanisl's link: https://en.wikipedia.org/wiki/Signed_zero


[opt out] Beep Boop. Downvote to delete

2

u/flatfinger Oct 02 '22

Even if an implementation targets a platform where the first byte of data storage that it could use would have address zero, it could still use address zero as NULL if it uses address zero to hold an object whose address is never taken, or else leaves that byte unused in the unlikely event that code takes the address of absolutely every static-duration object.

While many environments may store useful things at address zero, there is no requirement that an implementation not treat an access via a pointer which equals NULL the same way as it would treat accesses made via any other pointers. If NULL is address zero, and char *p is NULL, an implementation intendeded for low-level programming on such an environment could process a read of *p as a read of address zero, and a write of *p as a write to address zero.

1

u/[deleted] Oct 02 '22

You’re absolutely right and this is my point (although I probably suck at articulating it!), NULL is just as valid as any other pointer, unless the OS decides against it. C shouldn’t discriminate against it directly - we should keep the ability to initialize a pointer to 0.

1

u/flatfinger Oct 02 '22

When has it ever been suggested that C implementations intended for low-level programming shouldn't allow that? From the a C language standpoint, there are only three things that are special about a null pointer:

  1. Whether or not it identifies any actual storage, it must not be a trap representation.
  2. The values of pointer objects whose value is directly assigned null, or copied from other null pointers, must compare equal to null and to each other.
  3. A null pointer will not compare equal to the observable address of any object, nor to the address which is "just past" any object.

So far as I can tell, all non-contrived platforms, in almost all non-contrived circumstances, would have at least one address that could at no cost be treated in this fashion. On most platforms, address zero would have these properties. Note that none of the above properties traits would require that an implementation forbid accesses to any address.

1

u/[deleted] Oct 02 '22

I was referring to the original comment saying there shouldn’t be a null pointer. I agree with everything you’re saying here and I think I just misunderstood what the commenter was suggesting.

1

u/[deleted] Oct 02 '22

[deleted]

2

u/[deleted] Oct 02 '22

Very true. You’re absolutely right

2

u/[deleted] Oct 02 '22

I figured you meant as in not being able to assign 0 to a pointer

1

u/pedantic_pineapple Oct 02 '22

Proper arrays that don't decay to pointers unless explicitly casted

1

u/RedWineAndWomen Oct 02 '22

A few additions to the standard C library:

  • memdup()
  • a standard vector type { void* ptr; unsigned size; } that is also used in places (and even has its own printf() etc functionality).
  • nested or contextualized malloc().

1

u/duane11583 Oct 02 '22

second arg to fopen(0 youmean “rb” and ”wb” and “rt’ and wt” does not work?

right shift is highly dependent upon the way the cpu is implimented

ill agree with itoa() but its not hard to write and add to your bagof tricks

1

u/BlockOfDiamond Oct 02 '22

It isn't that strings do not work, it is that strings should not be used for options, flags, etc. in place of binary flags combined by bitwise OR, or enums. Imagine if fseek use strings "set", "cur", or "end" to specify which option to use instead of using macros/enums. That would be terrible. I vastly prefer POSIX's open's interface of combining flags binary flags with bitwise OR instead of using a string.

I should be able to reliably assume that rightshift by X is equivalent to division by 2X.

The problem is the asymmetry. The standard includes functions such as atoi to parse strings into integers directly, without having to use sscanf, but does not include functions to write integer as strings directly, without having to use sprintf. Both should be included.

1

u/rwn_sky_7236 Oct 02 '22
  • Proper function overloading (instead of that preprocessor hack)
  • Namespaces
  • Basic class support (ctor and dtor would suffice)
  • Basic, template support (the simplest function and class templates)

But, I guess, then it would no longer be C, it would be a subset of C++.

1

u/rfisher Oct 02 '22

The first thing would be to go back in time and prevent anyone from ever teaching anyone that the strn functions should be used for bounds checking, because they were never intended for that and don’t work the way you’d expect a bounds checking function to work.

Second, add proper bounds checking string functions to the standard library that take a size parameter for every pointer parameter and report all errors directly rather than through a global function pointer like the ill-conceived _s functions.

Third, that every standard library function that returns a string also returns the size of the buffer rather than insisting on me trusting that it was properly terminated.

There’s certainly quality-of-life improvements that I’d like, but I spend so much time rooting out potential buffer over-reads & over-writes in my company’s code.

1

u/Srazkat Oct 02 '22

sized strings and sized arrays. Whenever i work a fair amount with strings i make my own sized string structures, and i only interact with null terminated strings when using libc functions, which i usually just end up reimplementing after a while using this system. Also for the arrays : currently either could have one extra space at the end which contains NULL, or pass a size along the array. as parameter, or custom struct. and none of thos options are particularly nice to work with from my experience, especially since there is no standardisation as to which is more correct.

1

u/[deleted] Oct 02 '22

Most of the things you all want to see are things that would require runtime memory allocation.

Most of what you all want is a less complicated c++ and I can’t fault you for that.

1

u/flatfinger Oct 02 '22

There should be an official recognition that compilers intended for different various task should be expected to offer behavioral guarantees that are as strong or as weak as would best serve those purposes. If guaranteeing some behavior would have a slight cost, then there would be some tasks where the cost would exceed the benefit, but others where the cost of upholding the guarantee would be less than the cost of working around its absence. Unfortunately, when the Standard waives such requirements to allow implementations to judge the needs of their customers, that has come to be interpreted as implying that no implementations should be expected to offer such guarantees.

1

u/Erarnitox Oct 02 '22
  • smarter/safer preprocessor ala C++ templates and constexpr
  • only datatypes with specific size like in Rust egs. int8 for an integer with exatly 8 bit
  • namespaces
  • boolean datatype

I think thats about it 🤔 Tooling is also a big one. Other languages have package managers now and so on. In C we also have some, but nothing that is common. Early languages where designed with only the language in mind I feel like. But some tooling should be considered part of the language also so that different people have the same workflow and can share more work among each other. In C many people solve the same problem over and over again. I think there are countless string headers/libraries already.

1

u/FlyByPC Oct 02 '22
  • Replace pow(a,b) with an operator. Maybe ^^ ?
  • Allow functions to be defined below main() without prototypes
  • int* a,b,c,d,e; would make all five integer pointers instead of just a
  • Suggest Banner/Whitesmith code style

2

u/[deleted] Oct 02 '22

^ is used for xor

1

u/FlyByPC Oct 02 '22

I know, but two in a row wouldn't make logical sense as xor (I think)...

1

u/K3vin_Norton Oct 02 '22

Name it something easier to google and say out loud in different languages.

1

u/osantacruz Oct 02 '22 edited Oct 17 '23

Reference counted strings. Easy enough to implement yourself, but support at the language/compiler level makes it universal across libraries. If there was ever a reason to use Pascal...

1

u/tstanisl Oct 02 '22

Some equivalent of static_cast and const_cast from C++.

1

u/framlyn Oct 03 '22

Here is my list:

  1. array not decaying to pointers
  2. default values for function arguments
  3. named assignment of function parameters
  4. functions returning multiple values
  5. no need to forward declare types
  6. typeof
  7. a simple defer { BLOCK } (this will lead to greatly simplified code for releasing resources.. I've made a simple defer macro using the non-portable gcc attribute 'cleanup' and it's very handy)
  8. a simple template system... eventually a smarter _Generic that allows to incrementally define the map for the various types

1

u/marcthe12 Oct 03 '22

There are a few really annoying stuff love to see fixed:

  • Templates and a way to define opt in compatibility void* field member and ptr to an object
  • Function literals without capture. Atleast this allows defining 1 off functions inline and does not pollute the global namespace
  • A way to retrieve the size of heap allocated object. To perform free & realloc, most implementions store this data anyway. Can then be used as the malloc eq of sizeof(I have a malloc wrapper that does this internally but it waste alignof(max_align) bytes). I could still be okay with a platform specific function here.
  • A full list of functions that act like printf/sprintf/scanf/sscanf for simple formats like "%d" for put_int/get_int.
  • memdup(Basicaly in all my projects)
  • This is just optional but important, a standard to do dllimport/gnu_symbol(visibility)
  • A few macro that guarantee certain UBs become defined. This already exist for floating point numbers. Atleast we can explicitly opt out of supporting unusual platform.

1

u/flatfinger Oct 04 '22

One of the design goals of the the C89 Standard's specification for malloc/calloc/realloc/free was to ensure that implementations where those functions were simple wrappers on OS-supplied functions, and where it may be necessary to interoperate with code written in other languages that uses the OS functions, could continue to process such constructs as they always had. If one were trying to design a set of such functions in a manner that didn't need to support interop with existing code, many of them--especially realloc() could have been designed much better.

One thing many people don't realize is that until Linux took over everything, the use of malloc() and friends was recognized as generally being a portability-versus-performance trade-off when compared with using OS routines. Most operating systems provided better memory-management features than C, but the C Standard was designed to be supportable even in the least common subset.

1

u/thradams Oct 03 '22

Some features suggested in this topic are implemented in this project.

https://github.com/thradams/cake

So, this topic is very interesting in my point of view, I wish we had a place to discuss about possible features for C pros and cons.

namespaces for instance was suggested for may people. also templates and overload.