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)