Non-conforming C++: the Secrets the Committee Is Hiding From You - Miro Knejp - CppCon 2019
Vložit
- čas přidán 30. 09. 2019
- CppCon.org
Discussion & Comments: / cpp
Presentation Slides, PDFs, Source Code and other presenter materials are available at: github.com/CppCon/CppCon2019
-
Non-conforming C++: the Secrets the Committee Is Hiding From You
These days everyone talks about conforming and portable C++. Compiler vendors celebrate increasing conformance. Committee agents blind us with new shiny toys coming to the language. But there is a darker side to C++. A C++ you are not supposed to know about.
What if I told you there was more to C++ than what the agents of The Committee want us to believe? Over decades programmers all around the world have added features to the language in form of compiler extensions that let us do even greater things. Some are completely new, and some are lifted from C to C++ to allow some interesting, and sometimes more efficient, application.
We will see how statements can become expressions, how "goto" with extra superpowers can make your programs faster, and why there exists an operator named after a famous rock star. These are just a few examples of what to expect as listing any more would draw unwanted attention from The Committee. Unfortunately, because these extensions are not part of ISO C++, using any of them comes at the expense of portability. Or does it?
-
Miro Knejp
Miro wrote his first line of C++ code in 1997 at the age of 12, and it has been his programming language of choice ever since. He’s especially passionate about low-level programming, assembly, 3D graphics, and games engineering. Miro holds a Master’s degrees in Computer Science from the Technical University of Munich. He has worked on projects ranging from designing 3D rendering libraries to building airport self-boarding control systems. He currently works as freelancer and trainer, with the goal of creating his own video game one day.
-
Videos Filmed & Edited by Bash Films: www.BashFilms.com
*-----*
Register Now For CppCon 2022: cppcon.org/registration/
*-----* - Věda a technologie
Best talk by a man wearing a tinfoil hat I've ever seen.
Glad to see a talk that makes sense, I mean the intermolecular interactions with the classes of carcinoma neurosis always caused problems with inheritance
Dude's a great charismatic speaker and presenter.
OOOH This video DELIVERS!!!
Phenomenal slide transitions. Absolutely top notch.
Flexible Array Members are a part of standard C since 1999, same for designated initializers.
This is so awesome! Thank you Miro :)
Thank you for the CE talk on Monday. I learned about conformance view and used it to get the compiler versions for my slides the next day. You were also right about the ABAB pattern prediction, so thanks for telling me. The Hallway Track certainly is one of the best!
I love this talk... have been watching it a couple times just for pure entertainment. Let's disobey the committee!
Just rewatched for my third time while playing games. Its enjoyable!
Cool presentation! After seeing the flexible array part, I have some code to review...
5:00 Depending on the situation a better reply may be: "GCC explicitly allows accessing the bytes of a union member via another member of that union. In fact it even allows this if the members have different types." Of course this is only a useful reply if you don't need portability to compilers other than GCC (and presumably clang, which aims to be GCC-compatible).
42:00 That's really good. One thought: A smart compiler could theoretically take a normal switch statement and inline the jumps the way you do with computed gotos, no? Maybe they should do it by default for switch-based loops with a relatively simple switch expression?
I swear, there's so many things that make sense (like range cases in switch statements) in C++ but aren't in C++
Refreshing talk! Cool!
There's also std::align if you don't want to do the alignment arithmetic yourself (although often I do the arithmetic anyways because I can never remember the order of arguments to that function)
I think std::align is a bit overkill... most of the times the arithmetic is pretty doable on its own
For the flexible array member section, you can make a template function with an unsigned as a template parameter and changing the value of the unsigned corresponds in compiler generated static vector
That wouldn't work with runtime values which is what the whole problem was about, if you had the number at compiletime you could just use a statically sized array to begin with
49:55 is comedy gold. Plus all the stuff before ;P
I swear they were even hiding this video from me.
Incase it's been burried in the comments the part about placement new at 24:40 seems to have been identified as a defect and updated. It now explicitly states "except when referencing the library function operator new[](std::size_t, void*)"
Great talk!
This is the stuff of legends.
I loved the portal joke
😂😂😂 what an awesome and funny take on it
32:42 - faster goto... Never expected to see that. LOL.
That's some next level stuff
I didn't know GCC is so cool. I think I should use it more often. On the other hand the code also isn''t portable anymore.
There is portability table near the end of the talk. MSVC is the only incompatible compiler of the commonly used ones even a decade ago.
tinfoil hat mode engaged
10:51 the uint values for cyan and yellow are reversed. Good talk, cool hat (every C++ engineer has one, real or imaginary)
It's being stored red-lowest. Note that cyan = green|blue and yellow=red|green
with that indirect goto trick, could you instead put everything into functions and have an array of function pointers with tail call optimisation this should produce the same code without putting yourself on the committee's hitlist
If you can get the compiler to inline all those indirect calls, maybe. I haven't tried yet. Though Jason Turner tried the tail recursion variant, and of course the compiler turned it into a loop which then produced the same assembly as the switch loop.
@@mknejp According to: czcams.com/video/ieERUEhs910/video.html it won't. And as far as i understand it (which isn't very far) it can't.
I've hit this performance difference myself in the 2006 ICFP programming contest (they had you implement a virtual machine for a made up assembly language just to get started). Both the switch statement & callback approaches were slower.
@@Germanwtb It does work with function pointers, just got it to work: godbolt.org/z/XVfy3x
A brilliant suggestion and comment!
I'm sorely disappointed that the most straightforward and obvious way to implement this, which is switch, is not the fastest. This is wrong.
Pretty advanced content
For the operator new[] kerfuffle, couldn't you construct a std::array instead?
13:30 I have problems getting the ranged or the mixed types of designated array initializers to work in c++.
In C they work fine.
Errors are ranging from a compiler giving me the :
"sorry, not implemented" (no joke, this is litteraly the error message) error
to
"either all initializer clauses should be designated or none of them should be"
I guess these are the features we dont get, or have I missed something here?
It seems like GCC is back-porting the C++20 rules for designated initializers to this, as in C++ designated and positional initializers cannot be mixed. It's weird considering array initializers aren't even covered by C++.
At 28:40 : Is there any citation for why it's UB to treat the result of the last reinterpret_cast as a pointer-to-array instead of a pointer to a single object? If true, it seems that it would be impossible to implement std::vector in standard C++, as I'm pretty sure that std::vector instances just have to reinterpret_cast the void* from the allocator into a T pointer that is treated as an array (or I guess the allocator really does this -- the point is, at some point some chunk of memory gets cast and treated as an array). I know the issue is not the reinterpret_cast per se, but the idea is the same: an std::vector never uses an array new (placement new or otherwise), and the objects past the end iterator really are dead and not merely default-contstructed. Yet std::vectors seem to work without special compiler support, so what's the difference?
You are correct in your assessment of std::vector. It is indeed impossible to implement std::vector in standard C++ without any UB right now. The std gets a pass because they are part of "the implementation" and thus can do whatever it takes (and exploit knowledge about how "the implementation" works) to implement the specified observable behavior. begin_lifetime_as is one step towards making vector implementable, but I'm not 100% sure if that is enough to also fix the other reasons. As references go, there is [basic.object] specifying the only ways in which objects are created (arrays are objects too) and there is [basic.std.dynamic.safety] describing the rules of how "traceable pointers" work. In fact there is nothing in the standard guaranteeing that two objects of type T declared next to each other have the same memory layout as a T[].
@@mknejp "It is indeed impossible to implement std::vector in standard C++ without any UB right now"
Whaat? This actually blows my mind
12:20 While this works in clang, gcc only supports this for C code, not for C++ code ("sorry, unimplemented: non-trivial designated initializers not supported"). You can syntactically use designated initializers, but you cannot use them to reorder the initialization of members, rendering the feature close to useless. About the most luxuious thing you can do is skip struct members that have a default initializer. I think the feature added to C++20 has the same restrictions.
I know this is a year later, but it sounds like talking past each other. He doesn't care what order initializers happen in, he cares what order members of an array are stored as - and do they correspond to an enum.
Is the horror story in the 24th minute slide, the main reason why C++ was not considered in the Linux kernel?
A note for flexible array members concerning Microsoft's compiler: While gcc and clang use the double data[]; syntax, msvc uses double data[1]. That is the official blessed way to have a flexible array member and the same thing that Windows' own structs use. On every other compiler it would be undefined behaviour, because you'll be accessing elements past the first one, but on msvc it's the official way of doing it. Fortunately macros still exist.
Edit: I know I read this on MSDN somewhere but I can't locate the documentation now. If anyone finds it, please reply with the link.
Is it really UB if you allocate enough space?
@@Germanwtb Yes. Accessing an array of size 1 past index 0 is always UB. MSVC allows you to do it due to historical reasons with the Win32 API, which was created before flexible array members were a thing.
It is not undefined on clang and gcc, because the flexible array implementation is actually declaring an array with 0 elements, so it also accesses over the size. It doesn't matter if you have the defined size as 0 or 1.
@@mknejp it's UB according to the standard, but the implementation of all compilers for arrays is with pointer maths AFAIK (don't know if this is part of the standard), array[n] is equivalent to *(array+n), so accessing non-existent indices will return the next parts of memory, which might result in a "correct" result if that memory is actually allocated or a segmentation fault if the memory isn't allocated. If the memory you access happens to be the right data type (data types don't exist at runtime but you know what I mean) your program might even not crash and just use the wrongly accessed memory
Man, the clause on placement new[] is really disappointing. Is there any reason for that unspecified value? Placement new[] is actually impossible to use correctly with it...
I believe they use it to store the number of elements. Which is really unfortunate because you are saving this value yourself somewhere else and you technically cannot access this value to make use of it.
Is not this extra data to support overloaded placement array delete operator? It gets a size argument which cannot be deduced from the size of the type.
@@gergelynagy1225 That is indeed the reason why it exists. It is not needed for placement new operation though, as the data will not be deleted with delete[], but of course the standard says nothing about this so you're better off just not using placement new for arrays.
very informative, what's the name of the font used for code snippets?
Looks like Consolas to me...
It's Fira Mono - mozilla.github.io/Fira/, also check out Iosevka if you like this one
Fira Code, you can get it for free here github.com/tonsky/FiraCode
Tweets: twitter.com/mknejp/status/1148689298201415682, twitter.com/bstamour1/status/1148747992809299973, twitter.com/Cor3ntin/status/1149046823832698889
41:16 - goto is no longer faster? Plz, Xplane!
23:11 Any idea which Compiler with Version (and compiler flags) uses an unknown 'x' which is not 0. I tried to replicate the issue using the example from the slides as a base, and godbolt, but it seems like, at least gcc and clang don't have an issue with that. Instead of the doubles I also tried using a small struct with a constructor which prints the "this" pointer of the object and later the "data" pointer. So if "data" and "this" of the first element of the array is not the same, an 'x' was added.
39:31 why 0% branch prediction rate gets better performance than 100% prediction rate?
I think you misunderstand. The 0% prediction case is slower. The computed goto version is always at least as fast as switch, but faster in most cases.
@@mknejp Not always - It's not as fast as a topswitch in the special case of i$ exhaustion - if your total code size is bigger than your L1 instruction cache, the 20% inflation can cause a major performance hit.
If you're hitting i$ exhaustion in real life, your code might be too complicated ;)
@@mknejp In theory, the branch predictor (which is effectively a black box ATM) could be as accurate on "normal" switch (if the branch predictor "remembers" the location of the previous jump) as on the "computed go" switch. Though the "In theory" part doesn't happen in practice, LOL!
The flexible array part was... terrifying*.
Rust, take me with you, and I will try to appease The Borrow Checker as best as I can!
Just... don't let me deal with with this ...unholy abomination. I'm scared.
* the talk itself was EXCELLENT.
Couldn't you emulate a computed `goto` with tail calls? That feels like it should be possible...
Many of these should just be codified, because they are de facto in-practice standard in many compilers anyway.
hilarious man
@12:37 -> error: ISO C++ does not allow C99 designated initializers [-Werror=pedantic]
32:21 using reserve 👍😎
Using push_back 😭
The type bytecode seems to be simply an enum class or similiar, this would make it 𝘛𝘳𝘪𝘷𝘪𝘢𝘭𝘭𝘺𝘊𝘰𝘱𝘺𝘢𝘣𝘭𝘦 (copy is the same as move). So push_back is just as efficient as emplace_back for this type (it will generate the exact same code).
What's wrong there? He back-inserts (count - 1) opcodes then pushes one more halt instruction. Should all work out with no dynamic resizing.
Designated Initializers! Finally, C++ will be almost as useful as C.
really like the talk. 28:00 this data[0] = data[1] + data[2] bs is very disturbing. data[0] should be equal to *(data + 0) and data[1] should be equal to *(data + 1). if pointer arithmetic works correctly then this is defined and if this doesn't work as expected then this should be considered a compiler bug imo.
Pointer arithmetic also only works on arrays. The syntax is equivalent, yes, and thus both need the existence of an array object. Remember you're dealing with the abstract machine, not the physical machine. It cannot be a compiler bug if the compiler is following the rules of the standard.
Pretty sure pointer arithmetic on something that isn't an array is UB. However, I would expect that code to just work and wouldn't worry that much.
@@De5ertFi5h That's what people always say. "It works, don't worry" until suddenly compilers start optimizing based on this particular form of UB. It's an arms race that you cannot win because you're not following the rules.
@@mknejp I'm puzzled. Is this UB because the compiler can see where you got the pointer from, and that this pointer is not pointing to an array? If so, would this issue be solved by P0593R4 "Implicit creation of objects for low-level object manipulation", possibly using bless/start_lifetime_as, telling the compiler there actually is an array there?
Or... is it UB in general to subscript a pointer to a non-array object (beyond [1])? If so, how would that allow pointer-to-element types to work as an iterator? E.g. If I call begin with an array type I will also just get a pointer to the first element. How would you ever be able to get to / read the next element?
I just realized you can't use bless because you don't know the size of the array upfront.
So if im just C everything, how thick my hat have to be?
(expression) ? do_something() : 0;
also compiles
Yea of course, that's how you usually write it.
Hilarious
Nit: MFLOPS per second.
Flexible arrays are so basic and obvious thing to do. They are extremely useful useful. You would be an idiot not to use it. It literary does not make any sense to not use it.
Incredible presentation, both interesting and enjoyable. Those "computed goto"s are definitely going on my list of favorite ways to royally screw with anybody inheriting your codebase xD
Might want to delete that AD account of yours bro