Languages should be logical and require not a great deal of memory on the part of the user. (PS: This means that C++ is disqualified immediately. It is a language only a child can love. For ordinary folks, it is best avoided. The complexities are too great. It is also why java, swift, and others had to be invented.)

C remains important—and not just for its speed, universality, and the fact that everything else seems bootstrapped from it. C is also fairly logical and simple, which sets it apart from such languages as Swift, javascript, perl, php, etc. (and I often use many of them, too). I can remember C’s features even after not having used them for years. This is not just because C was my mother tongue. (Actually, BASIC and Pascal were.) It’s just … logical.

Part-time users cannot fix them. Language changes require agreement (and committees, like ANSI; or at least powerful implementors, like gnu, intel, and clang), so that their key features are widely spoken and universally understood. Languages have tool chains, which in my case includes the editor (emacs) and in other cases IDEs.

Language changes can incur some short-term pains, but their long-term payoffs can also be quite large. Alas, by 2016, there is one important aspect that has changed and that C has neglected:

C was designed when processors were slow and memory was precious.  (RIP.)

Compilation had to be fast, and this imposed extra burdens on the programmer to help. But the situation has changed. C has more to offer than compile speed. It still offers language logic, simplicity, and speed of execution. But we should no longer care primarily (or as much) about the speed of compilation and the memory use of the compiler as about the programmer.

C (or C.1) should shift many burdens from the programmer to the compiler. Many obvious improvements are not difficult. C could and should have long added them. None of these improvements would detract from C’s main virtues: programming simplicity and memorability. On the contrary. It should remain backward compatible, and add not too many features.

PS: I am not a language designer and compiler implementer. I am a language user. My suggestions are from the perspective of a user. I like overridable “conventions” instead of superfluous verbosity when I so desire it.

Obvious Improvements

  1. Variables should be declarable everywhere in C, just as in C++, and not just at the start of a scope block. And not just in gcc, but in all C compilers. (this is part of C99 already. so, ignore.)

  2. C should allow default arguments to function parameters, just like C++. Really! This is the #1 reason why I have to use g++ instead of gcc for many of my programs.

  3. Prototypes should be optional rather than be required. Requirement of prototypes is obsolete. There is no reason why C still needs linkers, loaders, .h files, and all the other vestiges of slow compilers and limited memory. Function prototypes are inferrable from the actual C code itself by the compiler. Thus, C programmers should just compile

     cc -o myprogram file1.c file2.c file3.c
    

    and let the compiler figure out the rest.

    Programmers should not be forced to repeat function definitions when the compiler can figure it out. There is no safety coming from repeating a copy of the .c file function definition in the .h file as a prototype. Again, the compiler can collect all the visible function declarations from the .c files, determine the prototypes for itself, and then work with inferred prototypes. There is no loss of execution speed, just a tiny bit of compilation speed.

    This change does not break backward compatibility where function prototypes are defined and included. When a project is so large that compilation speed matters more than programmer speed, then the prototypes can still be used (optionally). But even then, intelligent compilers checking file dates and writing cache files should still obviate the need for prototypes in .h files.

    It could also make sense to allow sizeof to read the size of structures that are otherwise static (private to the source file). It seems often more logical to place data structure definitions into the local .c file instead of into its .h file. C programs can then implement almost ‘real’ internal data hiding (encapsulation). Right now, the struct declaration often needs to go into the globally visible .h file, even though the client of my “class” (I know it is not a class, just a structure) should not even know about how the providing class encapsulates and implements it. The compiler could then just expose the functions.

  4. The entire standard ANSII C libraries should all be included by default. It should not even be necessary to write #include <allansi.h>. Programs should be assumed not to want to override standard ANSI functions. (When they do, depending on flag, the compiler could issue a warning. But it should remain allowed!)

    Why do I still have to remember that INT_MAX lives in limits.h? Or what is in stdlib.h, stdio.h, or strings.h? (Quick: where is bcopy?)

  5. The standard vsprintf definitions should be criminalized: why have the ANSI C designers not yet stolen the optional argument syntax from R?:

     void assert( condition, ... ) {
         if (condition) return;
         fprintf(stderr, ...);
         abort( );
     }
    

    it is more logical and easier. it is also very rare that programmers need variable arguments except to interface to the *printf and other variable functions. note that programs will be more user-friendly if this function is standard C. Friendly C programs—can you imagine this? (PS: the above function loses line number reporting, so this is not how it should be done.)

  6. From C++, C should steal the idea that structs can have methods (functions that operate on the data), preferably without all the complications of inheritance. They are supposed to be simple, not complex. Easy suggestion: variable.hello() should simply be always treated as hello(variable,...), i.e., merely as syntactic sugar with the check that a function named hello is defined in the struct of variable.

     struct {
             int v1, v2,...;
             function mymethod(...);
     } variable;
    
     variable.mymethod(...); // same as mymethod(variable,...)
     nonvariable.mymethod(...); // error
    
  7. Parameter should be nameable in the call. if the variable does not match the definition exactly, compilers should throw an error.

     void hello(int mynumber =2) {
       printf("your number is %d\n", mynumber);
        
     hello(3);  // ok
     hello(mynumber=4); // ok
     hello();   // ok
     hello(color=5); // error
    

    for simplicity, the C compiler could prohibit reordering of arguments. this eliminates a number of compiler ambiguities. unlike useless type checking (as in malloc), this feature is likely to reduce programming errors. it is optional, too. use it if you want. do not use if you do not.

  8. When there is only one valid construct, assume allowing it automatically. For example, malloc() etc. should rarely need type-casting.

     float *value= malloc( 15 );
    

    has an obvious meaning: everything other than an implicit (float *) before the malloc is a compiler error. So, just make the compiler assume it is there and move on. Or is C primarily a hazing IQ test? Ironically, this is how it used to be, before someone had the idea of more type-safety by making casts mandatory. But why? If there is only one reasonable interpretation, use it! Now, it is useful to permit type-casting—this can help make sure that the programmer can later read immediately what the left-side variable is, but it should not be necessary.

    Compiler assistance is useful if there are avenues for mistakes—such as defining variables to avoid misspellings (unlike, say, php). The danger of misspelling here is zilch.

  9. Nested functions would be nice. Why not allow

     int main() {
     char *strfix(const char *s, const int xtrabytes =0) {
             return strcpy( malloc( strlen(s) + xtrabytes + 1 ), s ); }
    
         char *s= strfix("hello");
         free(s);
     }
    

Sample Program

Now let’s consider coding a pseudo program for illustration. The following is not valid C:

    *main.c*:

    #include <cansi.h>

    int main() {
        time_t now= time();   // time is internally overloaded to time(time_t *timer = NULL)
        assert( now>0, "Time is not positive but %d\n", now);

        char *s= strfix("hello");
        free(s);
    }


    *strfix.c*:

    #include <cansi.h>

    char *strfix(const char *s, const int xtrabytes =0)
         return strcpy( malloc( strlen(s) + xtrabytes + 1 ), s );

Here is the “simple” C program that I think this requires:

    *main.c*:

    #include <stdio.h> // really? why needed?
    #include <time.h> // really? why?
    #include <assert.h> // really? why?
    #include <bool.h> // really? why?
    #include <stdlib.h> // really? why?

    #include "strfix.h" // really? why?

    int main() {
        time_t now;
        if (now==0) {
            // really? just to add an informative error?  why is a better assert not built in?
            fprintf(stderr, "Time is not positive but %d\n", now); 
            assert(false);
        }

        {
            // really?  why do I need to define a block just to define s?
            char *s= strfix1("hello"); // either one or two...or variadic messes
            free(s);
        }

        return 0;
    }


    *strfix.h*:
    
    char *strfix1(const char *s); // prototype.  really?
    char *strfix2(const char *s, const int xtrabytes); // prototype.  really?


    strfix.c:

    #include <strings.h> // really?
    #include <stdlib.h> // really?

    #include "strfix.h" // really?

    char *strfix1(const char *s) {
         return strcpy( malloc( strlen(s) + 1 ), s );
    }
    char *strfix2(const char *s) {
         return strcpy( malloc( strlen(s) + xtrabytes + 1 ), s );
    }

Uhhmmm…excuse me. Why was my pseudo program not understood by the C compiler?

Minor Further Possible Improvements

There are other modest compiler-improvements that would make C more pleasant.

  1. Allow starting C programs with specific C version desired, similar to how latex and perl can identify the compatibility version. This is to allow future changes to C.

     #require C 0.1
    

    If not specified, assume the latest standard is requested.

  2. Functions should be nestable. All it means is hiding the function name from the rest of the program. Maybe there was a language ambiguity reason that I do not understand here.

  3. There should be print() and scan() functions that figure out the arguments from the variable types, with interspersing:

     print("My firstvar is ", firstvar, " while my secondvar is ", secondvar, "\n");
     printf("My firstvar is %d, while my secondvar is %f\n", firstvar, secondvar);
    
     scan("My firstvar is ", &firstvar, " while my secondvar is ", &secondvar, "\n");
     scanf("My firstvar is %d, while my secondvar is %lf\n", &firstvar, &secondvar);
    

    Clearly, printf and scanf are irreplaceable, but such shortcut functions would sometimes be quite useful.

  4. All functions in C should be considered static (visible only to the file), unless defined otherwise (“export” or “global”), rather than the other way around.

  5. Two “##” at the beginning of a line could signal a comment, as they do in many other languages.

  6. A perl pod type set of standard documentation parameters could help.

  7. I would like optional #import for specific functions. This way, I could write

     #import strnlen,strncpy from strings.c;
    

    (When an import has been seen, assume that the program requires explicit imports of all external functions. Allow this to be overridden by a pragma.)

  8. For security, make it possible to define that a variable henceforth becomes read-only. If the compiler is smart enough, it can move the variable at this point into a hardware-protected memory region. If not available on a computer architecture, then it can be ignored.

     markreadonly(mynumber);
    
  9. Could the compiler steal from julia the idea that some functions should be allowed to exist with parameters that designate which of multiple functions with the same name be called? Nothing as complex as julia, but merely understanding of the basic data types: bytes, int, float, void, etc. abs() should be iabs() when the argument is an integer variable and fabs() when the argument is a double variable? not sure. Julia is clearly overkill in this respect for a language like C. It should only work for common data types.

Other Changes

I think the above changes would be trivial to implement and well worth it. They can be achieved with intelligent pre-processing of C code. They don’t need runtime changes. In contrast, some of the following are likely to elicit more disagreement, because they would require run-time implementations, too.

IMHO, the standard language and libraries should add standard libraries to support two special types, because they are so common:

  • strings with a library that lives without \0 semantics, but lets strings start with long.

  • hashes (dictionaries).

Of course, both already exist, but in too many non-standard implementations.

Strings

A new string type should abandon the end-of-string \0 semantics and the original strings should slowly be deprecated (except with a compiler pragma, they elicit warnings). The strings should use a standard definition that is the equivalent of a long (denoting stringlength) followed immediately by the character sequence. there should be complete libraries imitating the conventional C strings.h library.

string mystring;
for (int i=0; i<100; ++i) {
    sprintf(mystring, "%d", i);
    print("My string right now is ", mystring, ".  It is ", strlen(mystring), " characters in width.\n");
}

By fortunate accident, C did not define a string type as a built-in, so we can use the name string! Good foresight. One big problem is whether type-literal strings turn into char * or into string. this is for language designers to figure out. can the compiler determine this automatically?

Hashes

There should be one standard hash or dictionary library, with a string as its key and a pointer as its value:

void dosomething(string key, void *val) {
     static hash myhash; // garbage collected?
     myhash["first"]= NULL;
     myhash[key]= val;
     return myhash;
}

My life as a C programmer would be a lot easier if the above actually worked as expected. C would become a competitive language for quick-and-dirty jobs, with tradeoffs that are just a little different from perl, ruby, python, etc.

Garbage Collection

Why is there still no optional default dynamic string allocation? The compiler can now keep track of whether autodealloc still exists. If I use it, I lose program speed. OK. How much easier could my libraries become now??

I understand this adds complications. But it is important enough to deserve optional support.

Exception Handling

The better assert function on top would go a long way. A “catch” would be even better.

Better multidimensional array support