New C features in GCC 13
developers.redhat.com
developers.redhat.com
If you wonder why:
https://open-std.org/JTC1/SC22/WG14/www/docs/n2927.htm#exist...
See decltype section.
The real question to ask is why decltype(value) is int.
edit: Sorry, didn't mean to say 11.4 was obsolete. If you know of 11.3 machines that still exist, upgrade them!
I don't see anything about sparc generally, let alone sparc-solaris specifically[1]
[1] https://gcc.gnu.org/gcc-13/changes.html
[2] https://gcc.gnu.org/pipermail/gcc/2022-December/240322.html
https://www.oracle.com/a/ocom/docs/sparc-t8-m8-server-archit...
They rank high alongside delay slots, in harmful features.
The constraints applicable in the late '80s aren't the constraints applicable now.
Plus, this is a story about C, so I feel comfortable embracing the principle of explicit control flow. Delay slots just take that principle down to the branch prediction level.
I'm /very/ sad to see the unprototyped functions to go though -- that was my favourite way to catch newbies off-guard! Oh well ...
As for those who like to spend their Friday afternoon feeling sad about C++isms creeping into C: You are very late! You should have done that when GCC implemented ``__attribute__((cleanup))`` years^Wdecades ago.
edit: https://gcc.gnu.org/legacy-ml/gcc/2003-05/msg00528.html
https://www.godbolt.org/z/KaWzsc6qT
...in GCC it's still just a warning though.
Imagine the humble pie C would have to eat.
C beat out Pascal in the 80s for a lot of reasons, many of them not technical.
Pascal had the correct format for strings, the one you mentioned and the one used by all modern languages.
Imagine if after 40 years C would start using Pascal strings :-))
The first ISO standard to address this wasn't until 1990, with yet a new dialect called Extended Pascal, which of course wasn't compatible with the various earlier system-specific dialects, and anyway was way too late for Pascal to "win" over C.
See Wikipedia for confirmation: "Brian Kernighan, who popularized the C language, outlined his most notable criticisms of Pascal as early as 1981 in his article "Why Pascal is Not My Favorite Programming Language". The most serious problem Kernighan described was that array sizes and string lengths were part of the type, so it was not possible to write a function that would accept variable-length arrays or even strings as parameters."
C only became portable by bringing UNIX alongside via POSIX.
Then there were all those dialects, K&R C, Small-C, RatC, DBS C,...
Not really. The most crucial insight is that your string references should be fat pointers, which is not what Pascal does. Look at Rust's &str for the state of the art. Other choices are less important, but having string references be fat pointers is a huge boon. It would probably have seemed profligate in the 1980s, but it was the right choice.
ANS Forth[1], released 1994, used (addr, len) pairs throughout, eliminating most uses of “counted” (Pascal) strings in Forth-83 and earlier.
It works fine. The length has the same size as an address, the fat pointer being a pair of addresses is technically equivalent but has worse performance in practice so you should make it an (address, length) pair instead of course.
You can't exceed this length because of the pigeonhole principle, basically just arithmetic.
Is it worse though? Serious question, I’ve been wondering for quite a bit. IME processing a string from the start is easier with a (start, end) pair (one operation instead of two when you chop off things from the start). You can’t use those in standard C because it reserves the right to blow up on pointer subtraction essentially randomly (ptrdiff_t overflow is UB, and the only requirement for ptrdiff_t is that it hold 2^15-1; C89 doesn’t even tell you what the maximum allowed value is), but that’s not a performance problem, and not a problem at all in a different language.
The other three are first class types, tagged unions, and closures.
Standards committee says no.
Been digging through old newsgroups, and it sounds like one of the big reasons C won out over Pascal was because it was easier to find good C compilers in the 1980s compared to good Pascal compilers.
It didn't help that Pascal didn't have a usable standard, which is what led to articles like "Why Pascal is Not Brian Kernighan's favorite language", which also didn't help.
Edited to add: there was also some amount of antiestablishment fervor in the microcomputer community, and Pascal was for better or worse perceived as a somewhat ivory-tower language.
Want to write the string 'fat dog' on line 12 column 10? With Pascal you'd have to write a routine in assembly and call it. With C you use a trivial macro. Pascal was also generally very very slow. Almost as slow as basic which had better strings than Pascal.
MoveTo(10, 12);
DrawString('fat dog');
You could do worse.Anyway, Pascal was designed for teaching, and Modula-2 was designed for production code and systems programming.
UCSD Pascal, and Object Pascal also took many of their ideas from Modula-2.
https://www.cs.virginia.edu/~evans/cs655/readings/bwk-on-pas...
But you can get run-time bounds checking already: https://godbolt.org/z/jhcavobYj
Support for statically detecting problems is also (slowely) improving.
Apparently you mean something by "struct types" other than the well known C feature. Can you elaborate?
For example, Zig's slice type is a 'builtin struct' which contains a pointer and size, the builtin error union type is made of an error code and the actual result type. These things are not provided by the Zig standard libary, but instead built into the language.
In contrast, C's builtin types are all 'primitives' (pointers, integers and floats) - with the arguable exception of VLAs, which have been made non-mandatory in C11.
"Proper" strings, arrays, slices, tagged unions, etc... would need to be integrated into the language (unless you want to end up with a mess like C++).
https://github.com/uecker/noplate
Certainly not production ready.
For arrays you may be right, possibly a fat pointer like suggested by Denis himself.
For instance in a slice type, does this have a start and end pointer, or a start pointer and size? Does the pointer or size come first? Is size the number of elements, or number of bytes? Alignment? Padding? Etc...
Maybe yes, maybe no. Walter Bright, the creator of D, has a nice article on the subject [1]. I think Zig's approach which allows slices to decay to pointers and pointers+lengths to create slices is ideal. The real benefit of slices is the implicit bounds checking which some C folks might object to since one of the appeals of C is no "hidden magic".
Go/Java and generics for example. C++ and auto or closures. Javascript and async/await ("just learn regular Javascript async patterns").
The thing is C could have had a string type that knows its length since day one, with known performance tradeoffs, and also allow for everybody to build their own type in the more rare cases where it's needed.
And most of buffer overflow and string handling bugs would have been avoid, as most of the programs using them don't need the performance or special handling of C strings (or don't need them where the bugs occur, e.g. when reading some configuration file).
So working with C or C++, it is.
https://digitalmars.com/articles/C-biggest-mistake.html https://dlang.org/articles/d-array-article.html
They could call the language "Stone", and then we'd get to say "such and such program is written in Stone", "not written in Stone", etc.
If they keep adding features to C, such as type inference, won't C just ultimately become as feature-laden as C++, albeit on a much longer timescale?
What's the point of that?
The thing about typeof (and its close cousin auto) is that it exposes information that the compiler’s frontend has to know anyway for type checking. That’s why GCC has had typeof for many years, even back when it only spoke pure C. So while it’s a new feature, it doesn’t really have a lot of implications for the rest of the language.
As far as ISO is concerned, each edition of the C standard is made obsolete by its successor, but that doesn't impose any obligation on you or on compiler writers. If you have gcc, you can still use `gcc -std=c90 -pedantic-errors` (or c99, or c11, or ...).
https://en.wikipedia.org/wiki/Compatibility_of_C_and_C%2B%2B
sizeof('x')
equal to 4 if char letter = 'x';
sizeof(letter)
is equal to 1, just like `sizeof(char)`? If `'x'` is represented as an `int` in C, shouldn't `letter` in this example also be represented as an `int`?The type of letter was explicitly char, and sizeof(char) == 1 by definition in C.
char letter = 'x'; is a type coercion. That literal is an integer, with the value 120 and then it's coerced to fit in the char type, which is an 8-bit integer of some sort (might be signed, might not, doesn't matter in this case).
Rust has e.g. u32.to_le_bytes() to turn an integer into some (little endian) bytes, but I don't know if there's a trivial way to write the opposite and turn b"1ert" (which is an array of four bytes) into a native integer.
Edited to Add: oh yeah, it has u32.from_le_bytes(b"1ert"). I should have checked.
char *word = "xyz";
is a pointer to an array of four `int`s, `'x'`, `'y'`, `'z'`, and `'\0'`? When I evaluate sizeof(*word)
I do get 1 instead of 4, even though `*word` is pointing to `'x'`. Where are the remaining 3 bytes in memory?The C type system generally matters so little that the type of an expression has little relevance (sizeof is the most notable exception to that rule), which obscures this fact.
The initializer means that the char object it points to happens to be the first (0th) element of an array containing 4 elements with values 'x', 'y', 'z', and '\0'.
Most manipulation of arrays in C is done via pointers to the individual elements, and arithmetic on those pointers. (Incrementing a pointer value yields a pointer to the next element in the array.)
For example, `sizeof word` gives you the size of the pointer object, but `strlen(word)` yields 3, because it calls a library function that performs pointer arithmetic to find the trailing '\0' that marks the end of the string. (A "string" in C is a data layout, not a data type.)
char word[] = {'x', 'y', 'z', '\0'}; // sizeof(word) = 4, sizeof(*word) = 1
char word[] = "xyz"; // sizeof(word) = 4, sizeof(*word) = 1
What I did not realize was that the above two are not the same as this: char *word = "xyz"; // sizeof(word) = 8, sizeof(*word) = 1An initializer doesn't change that. It only affects the value stored in the object when it's created.
A special case exception is that an array object defined with empty square brackets gets its length from the initializer, so
char word[] = "xyz";
is a shorthand for, and is exactly equivalent to: char word[4] = "xyz";But apart from that, I would expect `{'x', 'y', 'z', '\0'}` to have size 16 rather than size 4 because it consists of four character literals which each have size 4 on my machine.
`{'x', 'y', 'z', '\0'}` does not have a type by itself, but it's valid syntax to use it to initialize various structs and arrays - some of those will have the size you are looking for, depending on which type of array or struct you choose to initialize with that: https://gcc.godbolt.org/z/Tqjq3xzKo
They are literally defined as "characters". sizeof(char) is always 1.
Your confusion (besides the pointer thing) is that 'x' is a funny way to write an int, not a char.
int numbers[] = {1, 2, 3}; // sizeof(numbers) = 12
> Your confusion (besides the pointer thing) is that 'x' is a funny way to write an int, not a char.Yes, this might be it. So the way to get a `char` value that contains "c" is to use type coercion and write it as `(char) 'c'`. This changes the representation in memory so that it now takes up only one byte rather than four, right?
Its size is one byte -- but the size of an expression isn't really relevant, since it's (conceptually) not stored in memory.
You can assign the value of an expression to an object, and that object's size depends on its declared type, not on the value assigned to it. The cast is very probably not necessary.
char c1 = 'c'; // The object is one byte; 'c' is converted from int to char
int c2 = 'c'; // The object is typically 4 bytes (sizeof (int))
The fact that character constants are of type int is admittedly confusing -- but given the number of contexts in which implicit conversions are applied, it rarely matters. If you assign the value 'c' to an object of type char, there is conceptually an implicit conversion from int to char, but the generated code is likely to just use a 1-byte move operation. char letter = 'x';
the initialization expression 'x', which for historical reasons is of type int in C, is implicitly converted to the type of the object. `letter` is a `char` because you defined it that way.If you had written
int letter = 'x';
that would be perfectly valid, and the conversion would be trivial (int to int).It's just like:
double x = 42;
`sizeof 42` might be 4 (sizeof (int)), but `sizeof x` will be the same as `sizeof (double)` (perhaps 8).Generic-programming with templates, a usable std::string, smart-pointers, references, the howl standard-library (streams, file access, containers, threads).
The controversial ones seem to be exceptions and classes. Exceptions affect programming flow, exception safety is very hard and the runtime costs are an issue depending on the environment. Class and inheritance are complicated feature, operator overloading is one of the best stuff I’ve seen. But I can understand why many programmers don’t want handle all the special rules involving classes.
And just one example (because it's one of my favourite topics where the C++ stdlib really failed):
std::string as a standard string type has so many (performance) problems that it is borderline useless. You can't simply use it anywhere without being very aware of the memory management implications (when and where does memory allocation and freeing happen - and std::string does its best to obscure such important details), and this isn't just an esoteric niche problem: https://groups.google.com/a/chromium.org/g/chromium-dev/c/EU...
Meanwhile, WG14, "not our problem ".
...By reading the C++ standard: "it's well-defined"
...By reading the C standard: *self-referencing Zalgo text quoted by dozens of StackOverflow thread debates where no completely confident conclusion is ever reached, although 3/4 people way smarter than you think it's well-defined and blame observations to the contrary on compiler bugs which they've reported to all the major compiler maintainers with varying reception from said maintainers as to whether they agree that those are actually bugs, forcing you, at the end of the day, to realize that you should really be writing your compiler's C, not aspiring to a universal, platonic ideal of C*
I get people don't like classes / templates / .. but there isn't any reason one has to use those.
Because they're orthogonal, and making function bodies less verbose with no loss of expressivity is nice, without needing to significantly alter the language?
Pretty much every C competitor has local type inference, and C actually needs more than most due to the `struct` namespace, typing `struct Foo` everywhere is annoying, and needing to typedef everything to avoid it is ugly.
Also C++ is missing convenient C features, like not needing to cast `void*` pointers, and designated initialisers were only added in C++20.
And the people who say "I Just hover the variable in my IDE" It doesn't work in a terminal with grep, you can't hoved a diff file and not even github do the hover thing.
Combine that with the implicit type promotion rules of C. Have Fun.
Nonsense.
> Combine that with the implicit type promotio rules of C. Have Fun.
This sort of trivial TI does not make that any worse. C is broken, it neither breaks nor unbreaks C.
Second-best time to plant a tree, and all.
Some platforms also just don’t have C++ compilers. Yes, they still exist. You buy some microcontroller, download an IDE from the manufacturer's web site, and you get some version of C with a couple extensions to it. And then there are all the random incompatibilities between C and C++, where C code doesn’t compile as C++, or gives you a different result.
> Some platforms also just don’t have C++ compilers
It's not like they're going to have C23 compilers either
Niche compilers like SDCC (https://sdcc.sourceforge.net/) are actually keeping track of recent C language improvements quite well.
It's what you get when your C compilers are implemented in C++.
* nullptr: fixes problems with eg, va_arg
* better enums: Who doesn't want that? C is a systems language, dealing with stuff like file formats, no? So why shouldn't it be comfortable to define an enum of the right type?
* constexpr is good, an improvement on the macro hell some projects have
* unprototyped functions removed: FINALLY! That's a glaring source of security issues.
Really I don't see what's there to complain about, all good stuff.
nullptr is an overkill solution. The ambiguity could have been solved by mandating that NULL be defined as (void*)0 rather than giving implementations the choice of (void*)0 or 0.
>Although it would have been a bit of a pain to adapt,
>an '89 or '99 standard in which the only source representation
>of the null pointer was NULL or nil or some other built-in token
>would have had my approval.
https://groups.google.com/g/comp.std.c/c/fh4xKnWOQuo/m/IAaOe...
Code written by C++ programmers.
Code written to be both C and C++.
It's called "change" and people don't like it.
-constexpr is not anything like constexpr in C++. -It makes no guarantees about anything being compile time. -It in no way reflects the ability of the compiler to make something compile time. -It adds implementation burden by forcing the implementations to issue errors that do not reflect any real information. (For instance you may get an error saying your constexpr isnt a constant expression, but if you remove the constexpr qualifier, then the compiler can happily solve it as a constant expression) -All kinds of floating point issues.
We should not have admitted this in to the standard, please do not use.
nullptr is the third definition of null. one should be enough, two was bad. why three?
Got any more information on that? Why does it fail in that way? Is that an implementation or a specification problem?
It fails for 2 reasons:
In order to make it easy to implement it had to be made so limited, that it in no way useful.
The second reason, and the real killer, is the "as if" rule. It states that any implementation can do what ever it wants, as long as the output of the application is the same. This means that how a compiler goes about implementing something is entirely up to the compiler. This means that any expression can be compile or execution time. You can even run the pre-processor at run time if you like! This enables all kinds of optimizations.
In reality, modern compilers like gcc, llvm and MSVC are far better at optimizing than what constexpr permits. However since the specification specifies exactly what can be in a constexpr, the implementations are required to issue an error if a constexpr does something beyond this.
> In order to make it easy to implement it had to be made so limited, that it in no way useful.
Such as?
> The second reason, and the real killer, is the "as if" rule.
Why is that a problem? It sounds like a benefit. It means that the optimization can't break anything, which to me is kind of the point.
Loops, function calls.... things available in C++
>Why is that a problem? It sounds like a benefit. It means that the optimization can't break anything, which to me is kind of the point
As-if is great! but the problem is that constexpr tricks people in to thinking that something is done at compile time, and the as-if rule overrides that and lets the implementation do it when ever it wants. constexpr is a feature over ridden by a fundamental rule of the language.
const int bla = 23;
const int blub[bla] = { 0 };
(see: https://www.godbolt.org/z/hjessMhGK)Isn't this exactly what constexpr is supposed to solve?
Maybe 'const' can't be fixed without breaking existing source code?
I don't really have a problem with adding a new keyword for 'actually const', maybe calling it 'constexpr' means C++ people have wrong expectations though, dunno.
For me, having constexpr being explicit about only accepting actual constant expressions, and creating an error otherwise (instead of silently falling back to a 'runtime expression') is fine, but the existing keyword 'const' has an entirely different meaning, there's just an unfortunate naming collision with other languages where const means 'compile time constant'.
If you look at CPPreference you can see how much complexity has been added to the C standard in the last few years.
In fact I don't see anything that needs support anywhere but the actual compiler.
(Enumeration constants are the identifiers defined inside enum xxx {...})
A lot of these ARE relevant and useful improvements to the C language itself; constexpr reduces the need for macro-constants (which is nice), ability to specify the enum storage type is often helpful and clean keywords for static_assert etc. are a good idea too.
And getting rid of the "void" for function arguments is basically the best thing since sliced bread.
const is sufficient to eliminate the use of macro-constants with the exception of the use of such constants by the preprocessor itself (in which case constexpr is also inapplicable).
#define DEF (ABC+(GHI<<JKL_SHIFT))I hope for your sake that you don't speak this way other than anonymously on the Internet; it reflects very poorly of you.
Also it can (in my opinion, brains seems to work differently) lower the cognitive load of a piece of code, by simply reducing the clutter.
Sure it can obscure the exact type of things, but I guess that's the trade-off some people are willing to do, at least sometimes.
Something like:
const auto got = ftell(fp);
saves you from having to remember if ftell() returns int, long, long long, size_t, ssize_t, off_t or whatever and in many cases you can still use the value returned by e.g. comparing it to other values and so on without needing to know the exact type.If you want to do I/O (print the number) then you have to know or convert to a known type of course.
This was just a quick response off the top of my head, I haven't actually used GCC 13/C2x yet although it would be dreamy to get a chance to port some old project over.
#noauto
No no no no nonononono. No!
Loose typing was a mistake. I think any sober analyst of C and C++ knows that. The languages have been trying to rectify it ever since.
But dynamic typing was an even bigger mistake. Perversely, it's one caused by a language not having a compiler that can type check the code, which C does.
I want to actually know what my code is doing, thanks. If you want "expressive" programs that are impossible to reason about, just build the whole thing in Python or JS. (And then pull the classic move of breaking out mypy or TypeScript half way in to development, tee hee.)
The only time `auto` is acceptable is when used for lambdas or things whose type is already deducable from the initializer, like `auto p = make_unique<Foo>()`.
In this case it would be long. I fail to see the huge risk you're implying by operating upon a long-typed value without repeating the type name in the code.
const auto pos_auto = ftell(fp);
const long pos_long = ftell(fp);
I don't understand what you can do with 'pos_long' that would be dangerous doing with 'pos_auto' (again, disregarding I/O since then you typically have to know or cast).Thank you for answering for me!
That's one reason type inference is safer than not inferring. Your program doesn't include semantics you didn't actually intend to be part of its meaning.
Also, I think lambdas would be annoying without it.
I meant that, to a person reading the code, `auto` tells you about as much about the type you're looking at as no type at all (like in a dynamically typed language).
This chatter said it better: https://news.ycombinator.com/item?id=35814337
Use size_t
std::unordered_multimap<string, std::unordered_multimap<string, someclass>> getWidgets();
With templates you can easily have very unwieldy types, and there's not that much benefit from spelling them out explicitly.Like any tool, there are good and bad uses of it. Well used, it removes unnecessary clutter and makes the code more readable.
#define MAX(A, B) ({ auto a = (A); auto b = (B); a>b? a : b; })
As-is, for such things I just use __auto_type, as it's already in the GNU-extension-land.How many of you guys have half of a C compiler lying around in a directory somewhere on your machines?
(And how do you find the time for WG14 AND writing code AND doing research? My cousin is in roughly the same field as you and you publish more than he does.)
But I'm not a big fan either.
https://thephd.dev/c23-is-coming-here-is-what-is-on-the-menu...
(TL;DR: it makes sense in macros)
And C-isms like '= { 0 };' instead of '= { };' never really made a lot of sense.
Even Linux kernel uses lot of GNU extensions. Computers are there to abstract stuff for us.
C++ is becoming more complex and bloated over time.
C, on the other hand, is becoming more convenient with almost no bloat or confusing features.
C is aging slowly but graciously, in my opinion.
Most of the good stuff is written in it: sqlite, curl, STB libraries, zlib, OS kernels etc.
Many are, but Linux moved to C11 (well gnu11 I guess) since v5.18
Summary of discussion: https://lwn.net/Articles/885941/
Actual move https://git.kernel.org/linus/e8c07082a810fbb9db303a2b66b66b8...
Being able to replace
int i;
for (i = 0; i < foo; i++) { ... }
with for (int i = 0; i < foo; i++) { ... }
makes it already worthwhile.Linus thinks so too:
> I think the loop iterators are the biggest user-visible thing, but
> there might be others.
Also interesting: > Of course, the C standard being the bunch of incompetents they are,
> they in the process apparently made left-shifts undefined (rather than
> implementation-defined). Christ, they keep on making the same mistakes
> over and over. What was the definition of insanity again?
-- https://lwn.net/ml/linux-kernel/CAHk-=wicJ0VxEmnpb8=TJfkSDyt...The issues here are:
- if it's defined, then people will rely on it, which means UBSan can't report it as an error if it sees it.
- IIRC x86 defines the overflow behavior differently for scalar and vector ints, so x86 compilers that want to do autovectorization would probably leave it undefined.
C's original sin here is that the numeric promotion rules are wrong ('unsigned short' should not promote to 'int'!) but as long as you can't fix that, you can't get rid of UB and still be performant.
That said, if risc-v is successful, many components will be written directly in assembly, and trash-abl code will be written in very high-level languages with risc-v assembly written interpreters (python/perl5/lua/javascript/ruby/php/haskell/tintin/milou).
Before the mid-late 1990s, programmers rarely needed to worry about the difference in size between 32 and 36 bit words.
It would be possible to do a quick-and-dirty work with preprocessor definitions. The real thing is when you want to write "fixed-C" with a legacy compiler: you realize than it does so much things without telling you, you would need a really accurate warning system in order to catch all those things. I was told gcc can report all integer promotions, true?
If you have a variable whose values are 0-10, then its type is an integer that goes from 0-10, not from 0-255 or -127-127.
C's success came from portability, so that would have killed it. Certainly you need fixed-size types occasionally to match externally-defined structures (hardware, protocols) but if you write u8 loop counters and u32 accumulators you're screwed on a DSP with only u24.
> That said, if risc-v is successful, many components will be written directly in assembly
There are already too many RISC-V extensions for code to be portable between different RISC-V chips without using a higher-level language.
This "portability" argument always rings hollow. How often are you actually reusing code between a DSP and a desktop? When real sizes don't matter, but just a minimum range, there's `(u)int_least8_t` (could be renamed as `u_min8`). On a DSP with only, say, 16-bit "bytes" (like the TMS320), that would internally be a `u16`.
C is not a "portable assembler" anymore. That's a myth. Almost no one writes C in a portable way. Every library or program has their on version of <stdint.h>. glibc, for example, uses gu8 for `unsigned char`. Put that on your 24-bit DSP, and `gu8` just became the same as `gu16` and the nonexistent `gu24`.
C is a language designed around a PDP/11 and inherits that legacy baggage. The C committee that refuses to accept the reality for "purity" reasons holds back the language.
The C committee is just adding stuff, to make even a naive C compiler more and more costly, which kills many "real life" alternatives, and in the end, does promote planned obsolescence more than anything else.
We have to remove stuff from C, not add stuff to C, make things more explicit and not more implicit (the abomination of the integer promotion...).
The stdlib has nothing to do in the C syntax even though not having memcpy and co internal to the compiler feels really meh on modern CPU.
And then there is the other stuff: calling conventions, per-CPU code generator, general IR optimizations. This can be very cheap if we accept poor quality of the generated code.
---
Edit: I inserted the word 'new'.
gcc -std=c89
still worksI was adamantly against Javascript adding OO-style classes and public/private methods and stuff for that reason. More stuff in the language makes it strictly harder to learn. Even if I don’t want to use those language features myself, I will inevitably end up reading and debugging other people’s code which does use those features. So I still need to learn it all, even if I don’t ever see myself using that stuff.
Knowing how much memory your numbers take up is important for many applications, so I find things like "auto i = 5" to be questionable.
Fancy compound literals seem like a solution in search of a problem.
Removing ancient unused misfeatures is good.
I don't have strong feelings about the rest. But I think people are reacting to the process more than the specific features. There's always a good reason for new features -- that's how feature creep works. Over time, adding a few features here and a few features there is how you go from a nice, simple language like C to a gargantuan beast like C++. C has around 35 or so common keywords, almost all of which have a single meaning. C++ has many more keywords (80+?), many of which have overloaded meanings or subtle variations in behavior, or that express very fine distinctions -- const vs. mutable or static/dynamic/const/reinterpret_cast, for instance. All of this makes C++ a very large language that is difficult to learn.
In a world of constant feature additions and breaking changes in programming languages, C is largely the same language it was in 1989. For some applications, that's a good thing, and there isn't another language that fills that niche.
Automatic variables[0] don't take up any defined amount of memory. They certainly don't take up exactly sizeof(i) memory.
Struct members and global variables are more likely to do what you say; in that case it will be either not be allowed or will be sizeof(int). Conversely, `long i = 5` is two different sizes (long vs int) which could be a latent bug.
[0] aka local variables, not `auto` variables
unreachable. No when the optimizer compiler can f*#k up my code if you combine it with unintended undefined behaviur. I just use asser(0) in debug builds. Not kidding.
#ifdef NDEBUG
# define UNREACHABLE() unreachable()
#else
# define UNREACHABLE() assert(0)
#endif
That's what I have been doing for years, except with __builtin_unreachable()... and __assume(0) if I bothered to make it work under MSVC.I am pretty sure many users are going to think it is correctness check an not an optimization attribute.
I often have a macro called UNREACHABLE() that evaluates to assert(0) or __builtin_unreachable() depending on NDEBUG.
It improves the generated code a bit.
One trick one can use is to define ASSERT() as a wrapper around assert() or something like
do { if (!x) unreachable(); } while (0)
This is a really nice way to tell the compiler about invariants -- and generate better code (and better warnings!).There are no fuck ups involved. None.
constexpr is great because it reduces the need for macros. There are three features that make almost all macro use unnecessary. They are enums, inline, and constexpr. Good enough inline support has only really been available for a few years -- by "good enough", I mean "doesn't slow down CPU emulator code".
Things are really looking up for C, in my view.
But I don't like any of them. I prefer to write my own code generators.
constexpr unsigned long long kMyBigNum = 0x1234123412341234ull;
Previously, you had to #define. Using enum causes problems when it doesn't fit in an int. And const doesn't mean the right thing: const int kArraySize = 5;
void MyFunction(void) {
int array[kArraySize]; // NO!
}
The above function will work if you have VLAs enabled, or if your compiler specifically allows for it. It's nice to have a standardized version that works everywhere (VLAs don't work everywhere).C++ doesn't exactly do "automatic typedef'ing of structures".
The difference is that in C++, if you define a type "struct foo", you can refer to it either as "struct foo" or as "foo" (likewise for class, union, and enum).
In C, if you define a type "struct foo", its name is "struct foo". If you want to call it "foo", you have to define an alias using "typedef".
Personally, I see "struct foo" as a perfectly valid name. I seldom feel the need to define another name for it. (typedef is the only way that a C type's name can be a single user-defined identifier; "int", "char" et al are keywords.)
I'll define a typedef for a struct type only if the code that uses it shouldn't know that it's a struct type.
Yes, it's a little extra typing. I save up the keystrokes I save by typing "{" rather than "BEGIN" and use them to type "struct". 8-)}
This is a matter of personal taste, and if you want to call it "foo", there are common idioms for doing that.
typedef struct { float x, y; } vec2;
...or if forward declaration is needed via a named struct: typedef struct vec2 { float x, y; } vec2;Compilers already know what you want to do as it will print an error such as: "unknown type name ‘Vec’; use ‘struct’ keyword to refer to the type".
struct S { ... };
typedef int S;
That's not valid in C++ (so would be a breaking change in C, if it were to adopt this).I don't really think changing this in C would break all that much code, but it's definitely not backwards compatible.
Is it redundant? I'm no c expert, but I remember several very specific cases where auto is necessary, and not using it leads to problems. Nested functions being the big one I remember.
The 'new' meaning for the 'auto' keyword doesn't seem compatible with this change. Won't these affect lots and lots of code that relies on nested function semantics?
It's a gcc extension but it is not used much. Many Debian/Red Hat packages that used it had it removed more than a decade ago because the implementation did things like stuffing code into the stack and executing it. When we began hardening our machines and disallowing that kind of behaviour, those packages either had to upgraded to not use that feature or be removed.
So in standard c, all function prototypes and definitions must always appear at toplevel?
In practice, this is not a big limitation by itself. Although C has function pointers, it's not a functional programming language -- there are no closures, and you can't create new functions at run-time under any circumstances. (Not within the language itself, anyway. You'd need to embed a compiler, or at least a minimal code generator.) On Harvard-architecture systems like microcontrollers, code and data may be in different memories (ROM vs. RAM) with different access permissions, and some platforms even put them in entirely separate address spaces.
int f(int x)
{
extern int g(int);
return g(x + 1) - 1;
}But, if the definition itself cannot appear in the enclosing block scope in the first place without the gcc extension, then I suppose an auto keyword here would still be meaningless (if not misleading / dangerous possibly UB?).