I said to myself, while watching the entirety of the video, "Man, this is a boring video".. Yet it was so interresting that I watched the entirety of it in one sitting
Compound literals are almost done right, except for the fact that there's no way to declare them as static const, which should otherwise represent the most common use case.
@@flatfingertuning727 maybe not explicitly, but you can still use 'em that way in certain situations. E.g. outside of a function, you could do this: struct s { int a; int b; }; static const struct s *const VARNAME = &(struct s){ .a = 10, .b = 11, };
@@ukyoize Better "Nate" than lever, I guess? The dialects supported by C compilers improved a lot between 1974 and 1999, but C99 only incorporated a few of the improvements, added some total garbage. Many more years have passed since C99 than between 1974 and 1999, but "design by committee-itis has stifled any useful evolution or fixes.
37:45 I tried to do just this in godbolt a few days ago. If inline function gets inlined (ex. when using -O3) it will compile and linker won't complain, because there is no function call in the code. If you disable optimizations, function body won't get generated by the compiller (unless you declare it somewhere), but the function call will, and linker will complain. This happens in both GCC and Clang. MSVC doesn't care and just generates the body.
alloca isn't standard, and has most of the same drawbacks as VLAs, most notably overflowing the stack if you don't check if the size is below some safe maximum, and if you are checking that, there's little reason to not always allocate that maximum size on the stack with a completely ordinary array.
@@Zeturic Mostly comes up in embedded systems where the stack and heap are just slices of the same global address starting at opposite ends. If you use the heap, you have to deal with fragementation yourself, and have to worry about the stack and heap growing into each other.
@@lperkins2 The "hope for the best" semantics you describe may be convenient, but make it impsosible to really know whether an application will work reliably. Better is to have a fixed-sized stack, but try to minimize its usage, and then carve storage for other objects out of whatever space remains.
@@flatfingertuning727 What "Hope for the best" strategy am I suggesting? If you have all your necessary data stored end-to-end in a large block of memory, and it doesn't fit, how you lay that out doesn't matter. If that is not the case, putting all *dynamic* data on the stack is the most efficient general memory layout, as it neatly avoids fragmentation. Keep in mind some platforms don't even *have* malloc out of the box, because you *really* don't want to use the heap when you're in the
ILE/C and C++ on IBM i is a case where there is no integer type that can hold a pointer (ints max out at 64 bits, and pointers (in most cases) are 128 bits.
@@AJMansfield1 The answer is complicated. There is actually only 64 bits of machine address tucked inside the 128 bits. IBM i uses a 'single level store' model where permanent storage is allocated and referenced by virtual address. Most systems have a file system with directories and paths built on top of disk storage. IBM i uses addresses (the same type of address used by the CPU) to identify storage just above the disk layer. These addresses are persistent-- when you allocate storage and get an address for it, that address can be used indefinitely across reboots, OS upgrades and in some cases even across machines. To make all this work and provide security, the pointer is encapsulated in a larger 128 bit pointer. This provides some type information (pointers can be to simple data items, to functions, to invocation stack frames or to encapsulated objects-- each of these is a different type.) There is also a 'tag' bit associated with every 128 bits of storage. If the tag is not set, the 128 bits is simply not a pointer. User programs can't manufacturer pointers like you can on other systems because they don't have instructions to set the tag bit, other than in appropriate ways (such as incrementing a pointer within bounds) Aren't you glad you asked?
@@tatoute1 The type of ptrdiff_t in C is 'int' so 32 bit signed. However, if you take int i = p-q; where p and q are addresses from different segments (e.g. one address of stack data and one heap location) and then add i to q, you will not get p, you'll get NULL. (this follows the C standard as taking the difference of pointers to addresses that are not to the same struct or array results undefined behavior.)
@@porky1118 In my case I prefer it over C#, C++ & Rust, haven't seen any objective C as yet so no opinions on that, wouldn't be surprised if I still prefer normal C though, I also tend to stick to C89/C90 just to save myself some hassle if anyone decides to compile my code in it, can't complain about a problem that don't exist :)
The indexing of 2 (or more) dimensional arrays at 49:58 can still use multiple brackets if you use some typecasting voodoo and tell the compiler that it's a higher dimension array: void example ( int *a , int *b , const int X , const int Y , int number ) { int (* a_array ) [ X ] = ( int (*) [ X ]) a ; int (* b_array ) [ X ] = ( int (*) [ X ]) b ; for ( int j =0; j < Y ; j ++) { for ( int i =0; i < X ; i ++) { b_array [ j ][ i ] = a_array [ j ][ i ] + number ; } } } You'll need to know the dimension size of the inner dimensions in the typecast but it will allow you to avoid the a[i*X+j] calculations. I'm not sure though if C90 will already allow for these typecasts but they've been quite handy for me when working with data allocated elsewhere in the program. From my experience using VLA's in C99 usually gives you data that ends up in stack which is not nice for larger arrays and can result in crashes as already remarked by the audience in the video.
I mean that's great but you end up having an additional pointer in the stack for every array you'll be manipulating, plus it prevents you from using restrict optimizations, usually my go-to solution is to use macros for indexing
This is a really comprehensive overview with good explanations. At 17:35 Dan says that a "byte" may be bigger than 8 bits. But I think he talks about a "char". (Or does any of the C or C++ standards refer to "byte"s?)
I haven't experienced it, I'm still pretty new in embedded programming, but, I have read that some machine hardware has defined size of bytes being larger or smaller than 8 bits
I think it’s the same in C++, but at least in C, the type `char` is exactly one byte big and CHAR_BIT (which thus is the size of a byte) is at least 8.
It is, a byte refers to the smallest addressable memory block, and there is hardware, especially older hardware with other sizes than 8 bits. More info: en.wikipedia.org/wiki/Byte
52:10 i belive in this context [n][m] are incorect, and it should be [n*m]; in function parameter list [] are nothing more than *, and x was not a ** (double pointer). we can use array's specific notation only for arrays declared in current scope.
Hello Dan Saks, as part of my input/hiring test for some company I've just found that C type(s) like int_least8_t, uint_least8_t, int_least16_t and uint_least16_t are written with small letter 'l', not with capitalised letter 'L'! ;-) But - maybe - of course it could be my misunderstanding/confusing from your choosen font used in your otherwise great presentation.. - On the screen ('flipchart') it appears (to me) as 'tall' (big, capitalised) character.
Then you'd be assuming that the newer standard, which by definition you don't know about when you're writing this code, is completely backwards-compatible with the existing one. Maybe you'd still do it if you were willing to live slightly dangerously, or maybe you'd output a warning if the version was greater (not equal). The safest practice would be to force a compilation failure if the compiler is targeting a newer standard than what you had in mind when writing your code, thereby forcing the person doing the compiling to either explicitly target the older standard or wait for you to patch your code to support the new standard.
Very good presentation - no cap. I think the next slide was about to explain the actual reason for the array parameter syntax - having `static` inside the brackets.
Interesting presentation, but it seems that there are even newer features such as those introduced in the augmented version of C programming language, features that solve language ambiguities etc.
gcc and clang support a pragma for associating a cleanup function to local pointer variables, provides a poor man's RAII - C community needs to formalize (a cleaner) concept of this into the standard so can be portable across all standards adhering C compilers. Was hoping for discussion about C11 _Generic keyword. what is the ultimate fate of Annex K bounds checking string functions? Any hope of a C library standard for iterating a char or wchar_t string and returning Unicode code points? (Getting code units is only useful for storage calculations - the holy grails is to be able to access strings for code points in portable, standard C manner. C standards folks should not be so lazy and try to tackle some of these issues that really matter and move the needle for working programmers
To be honest, your best bet is probably to just email him. It looks like he might sell training, so he may not give them to you for intellectual property reasons.
the exact width and minimum width integral types being optional/unknown-size and behaviour is a language-defect: You can not reliably use either. There is absolutely NOTHING that would prevent offering the fixed-width types on all platforms. An "int16_t" should still behave like a 16bit int, but if not natively supported by hardware it should give a compiler-error and a way to disable to error and force a software-fallback to use something like "leastN" with runtime checks to keep all computational results in the expected bounds. And for the bit-width it should also be mandated to have the common-ones as well as useful sizes that can/will be used in the future. So 8,16,24,32,48,64 - all can be found on not that exotic architectures, and then going up to 128,256,512,1024 - these are useful for finance, vectorisation, cryptography, simulation and large-numbers in general. And again if there is no hardware for this then a compiler-error and some flags to force a software fallback. Even worse is that they are not actually distinct types. C(++) really needs strong aliases.
I don't think declaring a volatile flexible array is useless ! It may mean that reading/writing in/out of that array does have side effects, so that no re-ordering/caching is permitted.
Volatile does not influence caching, nor prevent reordering by the CPU at runtime (it does not imply a memory fence of any type), it only prevents the compiler from doing certain optimizations at compile time.
@@ixp_ninja Maybe.. For memory fencing, cache, and whatnot, sure, I'm with you,. For memory access order, I am not THAT sure... but mostly if you read say twice from the same location in C (as seen symbolically) it will definitely instruct the processor to actually perform the fetch twice. If fetching just one byte, it will perform a single byte fetch, not a word fetch for example. If for example reading from a location has no side effect, say : void foo(char * volatile c) { char a; a=c|0]; a=c[1]; return; } without the "volatile" this *MAY* result in returning to caller without doing anything. With 'volatile' both single character reads *WILL* be performed in THAT order - at least the generated code will instruct the CPU to do so (Whatever the CPU reorders or not is beyond the scope of the language).
@@ivanscottw for this example I agree, but strictly speaking the compiler (and CPU) is still free to reorder operations around volatile storage (see e.g. ua-cam.com/video/w3_e9vZj7D8/v-deo.html), it just isn't allowed to remove the accesses completely, or reorder in a way that changes the side effects.
Given void() {int x[300000][4000]; ...} if x is allocated on the heap (which he says is possible) you are asking the RTS to allocate and deallocate for you (more that just a stack frame being deactivated). Are there RAII guarantees!? It seems that you have left the reason to even use C at this point if so.
Does it actually happen that people collide with the stdlib or some compiler internals? I appreciate the concern about symbol collision but it seems very far-fetched that people would actually run into it. On any big project I've worked on there would be some LIBNAME and all the symbols would be prefixed with LIBNAME unless they live in a file-scope or something smaller. For small programs if you really want to name your stuff along the lines of _iobuf I guess it could happen, but wouldn't the result usually be just a compiler error and then you refactor the code?
@@nathanrcoe1132 this sounds very plausible. Do you remember anything else about it? Was it used as an identifier or a macro of some sort? Was it easy to cleanup? Was it worth porting to C99?
@@yoloswaggins2161 I think it was just an identifier, and I don't think the code has been updated for the new standard because some platforms were not worth trying to get a conformant compiler for.
@@nathanrcoe1132 Alright well thanks for sharing. I didn't really consider it until you pointed it out then it seemed like the most obvious thing in the world.
If you re-declare an identifier, the compiler will give you an error, but in the case of variable shadowing, you'll get a warning at most, because it's allowed. Local variables typically don't have a library or other name as a prefix and may unintentionally mask global variables.
At 54:39, the source code example should read, assert(n > 0); packet *p = malloc(sizeof(packet) + (n - 1) * sizeof(data)); You can use (correction) sizeof(d[0]), too; it doesn't matter. Then, you don't have to worry about padding issues between packet.h and packet.d. No experienced C coder would write the code the way he did. Nevertheless there might be a better example use for this "offsetof" operator, but I get the point.
@@robegatt Well, as long as sizeof(d) == sizeof(packet) == sizeof(d[0]), then you can use sizeof(data), and so what data is a data type? You can use sizeof on a data type or a variable. When you use sizeof on a variable, C infers the type. On a 64-bit data type, sizeof(long) = 8 bytes, sizeof(int) = 4 bytes, etc. However, normally, I use ... assert(sizeof(d) == sizeof(packet)); packet *p = malloc(sizeof(packet) + (n - 1) * sizeof(d[0])); // Replaced sizeof(data) with size(d[0]), but keep in mind that both work.
@@robegatt Oh, yeah. You're correct. I'll make the correction. I looked at the original code in the video and presumed that he was using a variable, rather than the data type.
@@BruceBigby yes that what my "sigh" was aimed to. Calling a type with the identifier "data" is not the happiest of choices.... besides I tried and my compiler didn't complain but returned 0 as the size. Very nasty bug if it was some real code. You are right that you can take a sizeof() a type, that is basic stuff.
@@nickkotte9899 Actually, if you watch the video, you can see that the presentation gets cut short. He really only covers changes in C99. The C11 and C18 content isn't there, hence the need for the slides.
we should add some features from golang into C like receivers, defer statements, and anonymous functions (would make function pointers even more useful). The anonymous functions could compiled into some randomly named function or even inlined.
That's the kind of thing that we're doing with the C+ Project, except that we're pulling features from C++ because of the abundance of readily available C++ compilers and the propensity of legacy C code to migrate to C++.
@@maxabramson4781 but C++ is terrible. If I wanted C++, I would've just used C++. The point is make C better and just as good as C++ without making it C++.
@@G33KN3rd Okay, but the whole idea of the C+ Project is that we need the code to compile now while we're working on the standard. Hoping that it will work doesn't allow for testing, debugging, and benchmarking.
I am not sure about the enum statements at 44:27 . Why declaring and assigning values to n and m in enum enclosures? What does it mean to declare them this way?
Since everything in an enum needs to be known at compile time, this allows the user to set the size of the arrays there, which is needed at compile time. It is the same as #define. This enum trick can be used to calculate n! for example at compile time using recursion.
I wouldn't suggest using #defines... Text substitution is bound to break sometime. His two tokens ('m' and 'n') are const integer types local to the scope of their declaration. void test( void ) { { enum { foo = 3, bar = 7 }; printf( "%d %d ", foo, bar ); } // okay printf( "%d %d ", foo, bar ); // compile error: foo & bar unknown } Why he used two 'enum' statements is unknown. If C programmers can't reliably free alloc'd memory, can they be expected to #undef tokens after use??
I have to agree with your last acronym. C89/c90 is so last century. I fail to understand why folks can't embrace these changes, the new sized types and corresponding #include macros for printf/scanf are a godsend for portability. And as Dan pointed out the designated initializers are a great way to make the code more comprehensible.
@Tintin what amazes me is that there are vulnerabilities in presentation slides from people that are supposed "to teach" us the language. If so, what can we expect from just a regular dev?
Try to search for 'bit fields'. Maybe it could satisfy you requirement for bit manipulations. Remark Even forced alignment can be done by using size zero.
By definition, a "byte" is the smallest addressable unit. If you want a "bit" type, you either waste a whole intfast_t storing one bit to make things as quick as possible, or you waste time packing and unpacking bits from some kind of packed scheme. It's easy enough to roll your own if you need it and it will probably better suit your application than a standard implementation.
@@tissuepaper9962 IMHO, maybe it theory, but in practise you are not completely right. There are processor architectures, where the smallest adressable type is a "bit" type. For example Intel '51 and its bit adressable memory (addr range starts at 0x20, just after register banks). Even some ARM chips are able to do bit adressing (Cortex-M cores) - see so called 'bit banding' for more information. And last but not least, in general your information about "wasting time" is definitely not true. (You can look into instruction timing table for I'51, if you need facts.)
@@jardahybner9227 you're not understanding me. The C Standard *defines* a "byte" as the smallest addressable unit. It doesn't matter what size it is, there are systems that use 1-bit bytes.
53:00 VLA is not just convenience. "Allocating" memory on the stack in most cases is really just modifying a single register (the stack pointer)*, while allocating memory on the heap (with malloc, new, using a vector) can be very expensive, orders of magnitude slower! I don't get why it took 20+years for C++ to get designated initializer, and still not have it for arrays. Old features of C can be new features of C++ for years to come... C++ ... more like C+- *You are not limited to adding only (negative) constants to it, you can subtract a calculated amount to reserve exactly as much temporary storage as you need.
The language-version is "C17" - there is NO such thing as "C18". That simply does not exist. __STDC_VERSION__ is 201710L it is just iso has released the specifications for C17 in 2018.
I do appreciate C but all I hear is reasons why not to use C. It's become and educational language like Assembly, it is good to know but ultimately with modern languages like Rust and Zig, C is a relic of times past. I feel the same will be the case for C++. Sure, both languages still have code bases for software the world uses but and requires maintaining and due to cost or complexity these code bases may remain in C/C++, but new projects will be started in safer more modern languages, and slowly languages like C and C++ will be phased out.
Please understand that the C language is simple and small and beautifully formed to do the job it was created for. That is making it possible to make a maximally portable Unix. There is no reason C should change from now till the end of time. It does what it does. Brilliant! Meanwhile, C++ can forever add ever more and more complex and bewildering features. Whilst at the same same time trying to remain rooted in and backward compatible with C. This is not sustainable. I don't believe there is a single human that understands all of C++ and how it's parts interact with each other. It is nigh on impossible for the uninitiated to know when they have set a trap for themselves in C++. Remember the Vasa Galleon, listen to Bjarne's call for simplicity. Stop trying to convince C guys to jump on board the Vasa.
@@defeqel6537 The C11 threads API is optional and most standard libraries, including glibc, don't support it. Even worse, most don't even set the __STDC_NO_THREADS__ macro, despite that making the implementation non-conforming. So it's no surprise he doesn't mention it. Edit: obviously the macro name uses double underscore on each side, but I don't think youtube has an escape character for italics.
Communists: "But it was not real communism that one time" C++ believers: "But it was not a real good C++ implementation there" It's a bureaucrat's wet dream
Brian Kernighan addressed this decades ago in a paper _Why Pascal is Not My Favorite Programming Language_. Using a decent search engine you should be able to find a copy.
I have only one question. How do these innovations since 1989 help you write a good program rather than just introduce more pitfalls? If you want to top ANSI C, just invent a new language.
C90/C99/C11 *are* new languages, they just happen to share a lot with ANSI C. And the features mostly are there to simplify and eliminate boilerplate code for things the compiler can do for you. The less code you have to write (and the less code that is there just to jump through compiler hoops rather than express the algorithm) then the less defects that code will have.
@@JamesChurchill See text in bold at 2:54 . They are not new languages. They are new versions of the same language with strong retro-compatibility requirement. Any code that was compliant with any previous version of the standard MUST work as initially intended when compiled under the new version. C99 has all the pitfalls of C90 plus its own ones. C11 has all the pitfalls of C90 and C99 plus all its own ones. And so on for future versions of the standard. All those updates on the standard can only patch a few cosmetic things but they can't solve core problems. The best illustration is at 52:50 . The standard failed to enforce VLA, which is a major progress to reduce memory leaks, as a required feature in C99 compilers and had admit that failure publicly by falling back to optional in C11.
@@JamesChurchill No, they're not new languages as the newer versions have to be backwards compatible. They are improvements on the same language. If they were new, they wouldn't need to be backwards compatible and C99 would have "bool" rather than "_Bool" for example.
@@NeilRoy Whether they are backward compatible depends upon whether one views constructs which were defined in C89 but not in later versions as invitations to deviate from the old behavior *in cases where the new behavior would still meet programmer requirements*, or as deprecating any reliance upon the old behavior. If, for example, it would be acceptable for a program to either hang or produce meaningless outputs when fed invalid inputs, under C89 one could simply write a loop which would terminate when given valid inputs, and might not terminate when given invalid inputs, without having to include dummy side effects. If C11 had added a rule that would allow compilers to *cleanly* defer the execution of loops until the first observation of any side effects therefrom, or omit loops entirely if no such observation ever occurred, that would allow useful optmizations in cases where code never ends up using any values computed within a loop that might or might terminate. What C11 actually did, however, was to allow implementations to behave in completely arbitrary ways, which may include arbitrary memory corruption, if a program receives inputs that would cause execution to get stuck in a side-effect free loop. The net effect is that the only way to ensure that programs behave in tolerably-useless fashion when given invalid input is to write them in such a fashion as to block any otherwise-useful optimizations the rules would have allowed.
Wankers? The only wanker is the one who didn't pay attention to t he video and understand why. As he already pointed out, many people had ALREADY created their own macros using words like "bool" and "uint8" etc, so they needed to use keywords which were different and underscores like "_Bool" etc... were the answer.
It's a carry-over from C++ land where enunciating "ess-tee-dee" before everything from the standard library is clunky and extremely repetitive, so most people shorten it to something resembling "stood" or "stud".
#include or use GLFW or SDL or FreeGLUT to use OpenGL and BOOM you have access to the GPU. #include I think and BOOM you have audio. Well, you need first to install the correspoding libraries and link everything in the linker, but I doubt you know what I am talking about. Go back to Ruby, Javascript or whatever inefficient and crappy language you like.
As someone who used to program in C a long time ago, and who's coming back to the language, I found it a very useful presentation. Thanks!
I clicked because I thought this was a classical music composition.
Most vexing parse strikes again :P
😂
The Well Tempered Standard
It kind of is
I said to myself, while watching the entirety of the video, "Man, this is a boring video".. Yet it was so interresting that I watched the entirety of it in one sitting
Compound literals combined with designated initializers allow to design clear and elegant API styles.
Compound literals are almost done right, except for the fact that there's no way to declare them as static const, which should otherwise represent the most common use case.
@@flatfingertuning727 maybe not explicitly, but you can still use 'em that way in certain situations. E.g. outside of a function, you could do this:
struct s {
int a;
int b;
};
static const struct s *const VARNAME = &(struct s){
.a = 10,
.b = 11,
};
@@flatfingertuning727 c23 to the rescue!
@@ukyoize Better "Nate" than lever, I guess? The dialects supported by C compilers improved a lot between 1974 and 1999, but C99 only incorporated a few of the improvements, added some total garbage. Many more years have passed since C99 than between 1974 and 1999, but "design by committee-itis has stifled any useful evolution or fixes.
Such a dense material will take ages to digest. Appreciate your work!
Reviewing this again in JUune 2021.
37:45 I tried to do just this in godbolt a few days ago. If inline function gets inlined (ex. when using -O3) it will compile and linker won't complain, because there is no function call in the code. If you disable optimizations, function body won't get generated by the compiller (unless you declare it somewhere), but the function call will, and linker will complain. This happens in both GCC and Clang. MSVC doesn't care and just generates the body.
If your compiler doesn't support VLAs, you can use alloca to dynamically allocate space on the stack.
alloca isn't standard, and has most of the same drawbacks as VLAs, most notably overflowing the stack if you don't check if the size is below some safe maximum, and if you are checking that, there's little reason to not always allocate that maximum size on the stack with a completely ordinary array.
@@Zeturic Mostly comes up in embedded systems where the stack and heap are just slices of the same global address starting at opposite ends. If you use the heap, you have to deal with fragementation yourself, and have to worry about the stack and heap growing into each other.
@@lperkins2 The "hope for the best" semantics you describe may be convenient, but make it impsosible to really know whether an application will work reliably. Better is to have a fixed-sized stack, but try to minimize its usage, and then carve storage for other objects out of whatever space remains.
@@flatfingertuning727 What "Hope for the best" strategy am I suggesting? If you have all your necessary data stored end-to-end in a large block of memory, and it doesn't fit, how you lay that out doesn't matter. If that is not the case, putting all *dynamic* data on the stack is the most efficient general memory layout, as it neatly avoids fragmentation. Keep in mind some platforms don't even *have* malloc out of the box, because you *really* don't want to use the heap when you're in the
Exactly, I wish they standardized alloca() which is supported by every compiler anyway, instead of inventing a new syntax.
If C ever gets arbitrary precision integers, theres only one acceptable name.
long long long long long long long...
ILE/C and C++ on IBM i is a case where there is no integer type that can hold a pointer (ints max out at 64 bits, and pointers (in most cases) are 128 bits.
What's the purpose of having such wide pointers in that context?
@@AJMansfield1 The answer is complicated. There is actually only 64 bits of machine address tucked inside the 128 bits. IBM i uses a 'single level store' model where permanent storage is allocated and referenced by virtual address. Most systems have a file system with directories and paths built on top of disk storage. IBM i uses addresses (the same type of address used by the CPU) to identify storage just above the disk layer. These addresses are persistent-- when you allocate storage and get an address for it, that address can be used indefinitely across reboots, OS upgrades and in some cases even across machines. To make all this work and provide security, the pointer is encapsulated in a larger 128 bit pointer. This provides some type information (pointers can be to simple data items, to functions, to invocation stack frames or to encapsulated objects-- each of these is a different type.) There is also a 'tag' bit associated with every 128 bits of storage. If the tag is not set, the 128 bits is simply not a pointer. User programs can't manufacturer pointers like you can on other systems because they don't have instructions to set the tag bit, other than in appropriate ways (such as incrementing a pointer within bounds) Aren't you glad you asked?
@@johnvriezen4696 Thanks, that's really interesting.
And what is the type of the difference of 2 pointers?
@@tatoute1 The type of ptrdiff_t in C is 'int' so 32 bit signed. However, if you take int i = p-q; where p and q are addresses from different segments (e.g. one address of stack data and one heap location) and then add i to q, you will not get p, you'll get NULL. (this follows the C standard as taking the difference of pointers to addresses that are not to the same struct or array results undefined behavior.)
I still prefer C. Been using the newest version whenever possible.
Over what?
@@porky1118
Not C, presumably
@@porky1118 In my case I prefer it over C#, C++ & Rust, haven't seen any objective C as yet so no opinions on that, wouldn't be surprised if I still prefer normal C though, I also tend to stick to C89/C90 just to save myself some hassle if anyone decides to compile my code in it, can't complain about a problem that don't exist :)
The indexing of 2 (or more) dimensional arrays at 49:58 can still use multiple brackets if you use some typecasting voodoo and tell the compiler that it's a higher dimension array:
void example ( int *a , int *b , const int X , const int Y , int number )
{
int (* a_array ) [ X ] = ( int (*) [ X ]) a ;
int (* b_array ) [ X ] = ( int (*) [ X ]) b ;
for ( int j =0; j < Y ; j ++) {
for ( int i =0; i < X ; i ++) {
b_array [ j ][ i ] = a_array [ j ][ i ] + number ;
}
}
}
You'll need to know the dimension size of the inner dimensions in the typecast but it will allow you to avoid the a[i*X+j] calculations. I'm not sure though if C90 will already allow for these typecasts but they've been quite handy for me when working with data allocated elsewhere in the program. From my experience using VLA's in C99 usually gives you data that ends up in stack which is not nice for larger arrays and can result in crashes as already remarked by the audience in the video.
Bring more pain to the maintenance devs huh ?
I mean that's great but you end up having an additional pointer in the stack for every array you'll be manipulating, plus it prevents you from using restrict optimizations, usually my go-to solution is to use macros for indexing
This is a really comprehensive overview with good explanations.
At 17:35 Dan says that a "byte" may be bigger than 8 bits. But I think he talks about a "char". (Or does any of the C or C++ standards refer to "byte"s?)
I haven't experienced it, I'm still pretty new in embedded programming, but, I have read that some machine hardware has defined size of bytes being larger or smaller than 8 bits
I think it’s the same in C++, but at least in C, the type `char` is exactly one byte big and CHAR_BIT (which thus is the size of a byte) is at least 8.
It is, a byte refers to the smallest addressable memory block, and there is hardware, especially older hardware with other sizes than 8 bits. More info: en.wikipedia.org/wiki/Byte
@@humm535 A char doesn't have to be 8 bits e.g. if the hardware only supports 7 bit ascii
Edit: You're right, it's at least 8 bits
@@bene5431 Yes, a char does have to be at least 8 bits wide. If you don’t trust me, take a look at 5.2.4.2.1 _Characteristics of integer types _*__*
52:10 i belive in this context [n][m] are incorect, and it should be [n*m]; in function parameter list [] are nothing more than *, and x was not a ** (double pointer).
we can use array's specific notation only for arrays declared in current scope.
best c talk for me so far
Hello Dan Saks, as part of my input/hiring test for some company I've just found that C type(s) like int_least8_t, uint_least8_t, int_least16_t and uint_least16_t are written with small letter 'l', not with capitalised letter 'L'! ;-) But - maybe - of course it could be my misunderstanding/confusing from your choosen font used in your otherwise great presentation.. - On the screen ('flipchart') it appears (to me) as 'tall' (big, capitalised) character.
I learned a LOT from this video. Thanks 🙏
@11:55: Wouldn't it be safer to compare #if __STDC_VERSION__ >= 201710L ? Otherwise the code will break on a newer compiler, right ?
Then you'd be assuming that the newer standard, which by definition you don't know about when you're writing this code, is completely backwards-compatible with the existing one. Maybe you'd still do it if you were willing to live slightly dangerously, or maybe you'd output a warning if the version was greater (not equal). The safest practice would be to force a compilation failure if the compiler is targeting a newer standard than what you had in mind when writing your code, thereby forcing the person doing the compiling to either explicitly target the older standard or wait for you to patch your code to support the new standard.
@@Adamantium9001 It should still be checked for, or the code will assume it's C90
Very good presentation - no cap. I think the next slide was about to explain the actual reason for the array parameter syntax - having `static` inside the brackets.
Interesting presentation, but it seems that there are even newer features such as those introduced in the augmented version of C programming language, features that solve language ambiguities etc.
At 47:57 - I wish had known this earlier!
55:54, I just use a pointer there instead, something like:
typedef struct packet { header h; data *d; } packet;
...
packet *p = calloc( 1, sizeof(packet) + n * sizeof(data) );
data *d = (data*)(p + 1);
if ( !p )
return NULL;
p->d = d;
...
Gives plenty consistent behaviour
gcc and clang support a pragma for associating a cleanup function to local pointer variables, provides a poor man's RAII - C community needs to formalize (a cleaner) concept of this into the standard so can be portable across all standards adhering C compilers. Was hoping for discussion about C11 _Generic keyword. what is the ultimate fate of Annex K bounds checking string functions? Any hope of a C library standard for iterating a char or wchar_t string and returning Unicode code points? (Getting code units is only useful for storage calculations - the holy grails is to be able to access strings for code points in portable, standard C manner. C standards folks should not be so lazy and try to tackle some of these issues that really matter and move the needle for working programmers
Someone know where we can have the slides? I’m curious about the end of it, since it’s a very good talk.
To be honest, your best bet is probably to just email him. It looks like he might sell training, so he may not give them to you for intellectual property reasons.
Dan: "The // comment. That's an example of a slide that merits 3 seconds or less."
(proceeds to show the "// Comments" slide and talk for 84 seconds)
So is it wise to qualify all pointer parameters with restrict, unless I know there's a chance the data pointed to will overlap? Or is that overkill?
In the next revision of C standard (C23), bool, true and false will be keywords, like in C++.
I enjoyed this talk very much
the exact width and minimum width integral types being optional/unknown-size and behaviour is a language-defect:
You can not reliably use either.
There is absolutely NOTHING that would prevent offering the fixed-width types on all platforms. An "int16_t" should still behave like a 16bit int, but if not natively supported by hardware it should give a compiler-error and a way to disable to error and force a software-fallback to use something like "leastN" with runtime checks to keep all computational results in the expected bounds. And for the bit-width it should also be mandated to have the common-ones as well as useful sizes that can/will be used in the future. So 8,16,24,32,48,64 - all can be found on not that exotic architectures, and then going up to 128,256,512,1024 - these are useful for finance, vectorisation, cryptography, simulation and large-numbers in general. And again if there is no hardware for this then a compiler-error and some flags to force a software fallback.
Even worse is that they are not actually distinct types. C(++) really needs strong aliases.
I don't think declaring a volatile flexible array is useless ! It may mean that reading/writing in/out of that array does have side effects, so that no re-ordering/caching is permitted.
Volatile does not influence caching, nor prevent reordering by the CPU at runtime (it does not imply a memory fence of any type), it only prevents the compiler from doing certain optimizations at compile time.
@@ixp_ninja Maybe.. For memory fencing, cache, and whatnot, sure, I'm with you,. For memory access order, I am not THAT sure... but mostly if you read say twice from the same location in C (as seen symbolically) it will definitely instruct the processor to actually perform the fetch twice. If fetching just one byte, it will perform a single byte fetch, not a word fetch for example. If for example reading from a location has no side effect, say :
void foo(char * volatile c)
{
char a;
a=c|0];
a=c[1];
return;
}
without the "volatile" this *MAY* result in returning to caller without doing anything. With 'volatile' both single character reads *WILL* be performed in THAT order - at least the generated code will instruct the CPU to do so (Whatever the CPU reorders or not is beyond the scope of the language).
@@ivanscottw for this example I agree, but strictly speaking the compiler (and CPU) is still free to reorder operations around volatile storage (see e.g. ua-cam.com/video/w3_e9vZj7D8/v-deo.html), it just isn't allowed to remove the accesses completely, or reorder in a way that changes the side effects.
Great stuff
Only C is worthy of applause!
Given void() {int x[300000][4000]; ...} if x is allocated on the heap (which he says is possible) you are asking the RTS to allocate and deallocate for you (more that just a stack frame being deactivated). Are there RAII guarantees!? It seems that you have left the reason to even use C at this point if so.
That's a very nice explanation. Thank you.
Does it actually happen that people collide with the stdlib or some compiler internals? I appreciate the concern about symbol collision but it seems very far-fetched that people would actually run into it. On any big project I've worked on there would be some LIBNAME and all the symbols would be prefixed with LIBNAME unless they live in a file-scope or something smaller. For small programs if you really want to name your stuff along the lines of _iobuf I guess it could happen, but wouldn't the result usually be just a compiler error and then you refactor the code?
I have seen restrict used as an identifier in pre-C99 code to cite one example
@@nathanrcoe1132 this sounds very plausible. Do you remember anything else about it? Was it used as an identifier or a macro of some sort? Was it easy to cleanup? Was it worth porting to C99?
@@yoloswaggins2161 I think it was just an identifier, and I don't think the code has been updated for the new standard because some platforms were not worth trying to get a conformant compiler for.
@@nathanrcoe1132 Alright well thanks for sharing. I didn't really consider it until you pointed it out then it seemed like the most obvious thing in the world.
If you re-declare an identifier, the compiler will give you an error, but in the case of variable shadowing, you'll get a warning at most, because it's allowed. Local variables typically don't have a library or other name as a prefix and may unintentionally mask global variables.
What's the difference between "inline" and "static inline"?
with static inline, when a non-inline version is required, it becomes a static function in every compilation unit which needs it.
i love c, anything can be created with it
At 54:39, the source code example should read,
assert(n > 0);
packet *p = malloc(sizeof(packet) + (n - 1) * sizeof(data));
You can use (correction) sizeof(d[0]), too; it doesn't matter. Then, you don't have to worry about padding issues between packet.h and packet.d. No experienced C coder would write the code the way he did. Nevertheless there might be a better example use for this "offsetof" operator, but I get the point.
You mean sizeof(d[0]) ... "data" is a type (sigh) :-(
@@robegatt Well, as long as sizeof(d) == sizeof(packet) == sizeof(d[0]), then you can use sizeof(data), and so what data is a data type? You can use sizeof on a data type or a variable. When you use sizeof on a variable, C infers the type. On a 64-bit data type, sizeof(long) = 8 bytes, sizeof(int) = 4 bytes, etc. However, normally, I use ...
assert(sizeof(d) == sizeof(packet));
packet *p = malloc(sizeof(packet) + (n - 1) * sizeof(d[0])); // Replaced sizeof(data) with size(d[0]), but keep in mind that both work.
@@BruceBigby You wrote sizeof(data[0])... you cannot subscript a datatype.
@@robegatt Oh, yeah. You're correct. I'll make the correction. I looked at the original code in the video and presumed that he was using a variable, rather than the data type.
@@BruceBigby yes that what my "sigh" was aimed to. Calling a type with the identifier "data" is not the happiest of choices.... besides I tried and my compiler didn't complain but returned 0 as the size. Very nasty bug if it was some real code. You are right that you can take a sizeof() a type, that is basic stuff.
Are there any Text Books or Reference Books that have kept up with the latest standard?
C in a Nutshell (the 2015 edition) , is up to date with C2011.
Modern C
Once C was old sChool.
Now C is new sChool.
And new C is Cool.
Where can I find the slides...
In the video
@@nickkotte9899 Hahaha
@@nickkotte9899 Actually, if you watch the video, you can see that the presentation gets cut short. He really only covers changes in C99. The C11 and C18 content isn't there, hence the need for the slides.
github.com/ACCUConf/PDFs_2015/blob/master/Dan_Saks_-_New_Features_in_C.pdf
Here? It's from 2015 but I guess it's the same
we should add some features from golang into C like receivers, defer statements, and anonymous functions (would make function pointers even more useful). The anonymous functions could compiled into some randomly named function or even inlined.
That's the kind of thing that we're doing with the C+ Project, except that we're pulling features from C++ because of the abundance of readily available C++ compilers and the propensity of legacy C code to migrate to C++.
@@maxabramson4781 but C++ is terrible. If I wanted C++, I would've just used C++. The point is make C better and just as good as C++ without making it C++.
@@G33KN3rd Okay, but the whole idea of the C+ Project is that we need the code to compile now while we're working on the standard. Hoping that it will work doesn't allow for testing, debugging, and benchmarking.
@@maxabramson4781 àa
I am not sure about the enum statements at 44:27 . Why declaring and assigning values to n and m in enum enclosures? What does it mean to declare them this way?
Since everything in an enum needs to be known at compile time, this allows the user to set the size of the arrays there, which is needed at compile time. It is the same as #define. This enum trick can be used to calculate n! for example at compile time using recursion.
AFAICT it's basically to prevent the compiler from yelling at you about making a variable length array. The members of an enum are all const.
I wouldn't suggest using #defines... Text substitution is bound to break sometime.
His two tokens ('m' and 'n') are const integer types local to the scope of their declaration.
void test( void ) {
{ enum { foo = 3, bar = 7 }; printf( "%d %d
", foo, bar ); } // okay
printf( "%d %d
", foo, bar ); // compile error: foo & bar unknown
}
Why he used two 'enum' statements is unknown.
If C programmers can't reliably free alloc'd memory, can they be expected to #undef tokens after use??
My lab teacher at uni required code to compile in gcc with the -c89 flag. FML
I have to agree with your last acronym. C89/c90 is so last century. I fail to understand why folks can't embrace these changes, the new sized types and corresponding #include macros for printf/scanf are a godsend for portability. And as Dan pointed out the designated initializers are a great way to make the code more comprehensible.
Very interesting talk.
49:58 oops. That's a double-free bug. Or, actually m-times-free.
@Tintin what amazes me is that there are vulnerabilities in presentation slides from people that are supposed "to teach" us the language. If so, what can we expect from just a regular dev?
@ do you upvote your own comments?
@ jesus, dude, you need to relax. And maybe learn C syntax as well.
@ Machine formatting (a la clang_format) helps with this kind of bug. This is a cast where the indenting is misleading.
I love that the next point is "this is easy to get wrong." It seems so!
What's about the bit data type, which is often used in embedded systems ?
The only way to access the “bit” data is to just use code like:
char thing=25;
thing
Try to search for 'bit fields'. Maybe it could satisfy you requirement for bit manipulations. Remark Even forced alignment can be done by using size zero.
By definition, a "byte" is the smallest addressable unit. If you want a "bit" type, you either waste a whole intfast_t storing one bit to make things as quick as possible, or you waste time packing and unpacking bits from some kind of packed scheme. It's easy enough to roll your own if you need it and it will probably better suit your application than a standard implementation.
@@tissuepaper9962 IMHO, maybe it theory, but in practise you are not completely right. There are processor architectures, where the smallest adressable type is a "bit" type.
For example Intel '51 and its bit adressable memory (addr range starts at 0x20, just after register banks).
Even some ARM chips are able to do bit adressing (Cortex-M cores) - see so called 'bit banding' for more information.
And last but not least, in general your information about "wasting time" is definitely not true. (You can look into instruction timing table for I'51, if you need facts.)
@@jardahybner9227 you're not understanding me. The C Standard *defines* a "byte" as the smallest addressable unit. It doesn't matter what size it is, there are systems that use 1-bit bytes.
53:00 VLA is not just convenience. "Allocating" memory on the stack in most cases is really just modifying a single register (the stack pointer)*, while allocating memory on the heap (with malloc, new, using a vector) can be very expensive, orders of magnitude slower!
I don't get why it took 20+years for C++ to get designated initializer, and still not have it for arrays. Old features of C can be new features of C++ for years to come...
C++ ... more like C+-
*You are not limited to adding only (negative) constants to it, you can subtract a calculated amount to reserve exactly as much temporary storage as you need.
The language-version is "C17" - there is NO such thing as "C18". That simply does not exist.
__STDC_VERSION__ is 201710L
it is just iso has released the specifications for C17 in 2018.
Good video title for c, compliant with c conventions 😂 after 5 years new😂
I do appreciate C but all I hear is reasons why not to use C. It's become and educational language like Assembly, it is good to know but ultimately with modern languages like Rust and Zig, C is a relic of times past. I feel the same will be the case for C++. Sure, both languages still have code bases for software the world uses but and requires maintaining and due to cost or complexity these code bases may remain in C/C++, but new projects will be started in safer more modern languages, and slowly languages like C and C++ will be phased out.
Well, people have been saying this for decades, yet C and C++ stick around.
This presentation should have been called C99 features and equivalences in the C++ world.
If __STDC__ == 0, then you are defencing of the anciency.
Please understand that the C language is simple and small and beautifully formed to do the job it was created for. That is making it possible to make a maximally portable Unix.
There is no reason C should change from now till the end of time. It does what it does. Brilliant!
Meanwhile, C++ can forever add ever more and more complex and bewildering features. Whilst at the same same time trying to remain rooted in and backward compatible with C.
This is not sustainable.
I don't believe there is a single human that understands all of C++ and how it's parts interact with each other.
It is nigh on impossible for the uninitiated to know when they have set a trap for themselves in C++.
Remember the Vasa Galleon, listen to Bjarne's call for simplicity.
Stop trying to convince C guys to jump on board the Vasa.
Yes, C as it is is simple, clear, standard and clean...
Hope C People will never do as the People of "serial updates ++" do ...
simple provide #xinclude and this would check if already included.. done.. oh wait, that is C and not PL1
I think it's interesting how this guy pronounces std phonetically, like stood. Whensved I see std I pronounce is "sexually transmitted disease"
at 55:22 packet *p = malloc(sizeof(packet) + (n-1) * sizeof(data));
What if `n` is zero?
Missed casting the rvalue to a 'packet' pointer, didn't he...🤣
Casting results of malloc is not needed. Since malloc returns a void *, It can be assigned to any pointer variable. @@rustycherkas8229
slide 78 gave me a headache
The Chicago Bulls
clickbait title. it should be what is new in C99 standard
@@adrianliung8374 Didn't it leave uncovered everything related to threading that was introduced in C11?
@@defeqel6537 Yeah, he did focus a lot on C99. I would have liked to have heard more about C11 as well.
@@defeqel6537 The C11 threads API is optional and most standard libraries, including glibc, don't support it. Even worse, most don't even set the __STDC_NO_THREADS__ macro, despite that making the implementation non-conforming. So it's no surprise he doesn't mention it.
Edit: obviously the macro name uses double underscore on each side, but I don't think youtube has an escape character for italics.
Ko
i just want lambdas and template functions
They're the reason why I prefer C. I don't like the absolute mess they cause.
Lambas are useless, just make another function
Just use C++ then lol
As the old joke goes, C has the IOCCC (International Obfuscated C Code Contest). There is no C++ equivalent, C++ has templates.
lambdas are a gimmick.
Communists: "But it was not real communism that one time"
C++ believers: "But it was not a real good C++ implementation there"
It's a bureaucrat's wet dream
The tool has been used the wrong way. And taught how to be used in the most counter productive manner ever. "Education" like usual.
Oh dear God! Why didn't Kernighan and Ritchie just use Pascal?!?!
Brian Kernighan addressed this decades ago in a paper _Why Pascal is Not My Favorite Programming Language_. Using a decent search engine you should be able to find a copy.
Thanks for the cite. Good article. @@BrianHaug
This reminds me of TF2 spaghetti code
C in its entirety should remain forever a subset of C++
I have only one question. How do these innovations since 1989 help you write a good program rather than just introduce more pitfalls? If you want to top ANSI C, just invent a new language.
C90/C99/C11 *are* new languages, they just happen to share a lot with ANSI C. And the features mostly are there to simplify and eliminate boilerplate code for things the compiler can do for you. The less code you have to write (and the less code that is there just to jump through compiler hoops rather than express the algorithm) then the less defects that code will have.
@@JamesChurchill See text in bold at 2:54 . They are not new languages. They are new versions of the same language with strong retro-compatibility requirement. Any code that was compliant with any previous version of the standard MUST work as initially intended when compiled under the new version. C99 has all the pitfalls of C90 plus its own ones. C11 has all the pitfalls of C90 and C99 plus all its own ones. And so on for future versions of the standard. All those updates on the standard can only patch a few cosmetic things but they can't solve core problems. The best illustration is at 52:50 . The standard failed to enforce VLA, which is a major progress to reduce memory leaks, as a required feature in C99 compilers and had admit that failure publicly by falling back to optional in C11.
@@JamesChurchill No, they're not new languages as the newer versions have to be backwards compatible. They are improvements on the same language. If they were new, they wouldn't need to be backwards compatible and C99 would have "bool" rather than "_Bool" for example.
@@JamesChurchill D is a new language which has a version called "D as a better C"
@@NeilRoy Whether they are backward compatible depends upon whether one views constructs which were defined in C89 but not in later versions as invitations to deviate from the old behavior *in cases where the new behavior would still meet programmer requirements*, or as deprecating any reliance upon the old behavior. If, for example, it would be acceptable for a program to either hang or produce meaningless outputs when fed invalid inputs, under C89 one could simply write a loop which would terminate when given valid inputs, and might not terminate when given invalid inputs, without having to include dummy side effects. If C11 had added a rule that would allow compilers to *cleanly* defer the execution of loops until the first observation of any side effects therefrom, or omit loops entirely if no such observation ever occurred, that would allow useful optmizations in cases where code never ends up using any values computed within a loop that might or might terminate. What C11 actually did, however, was to allow implementations to behave in completely arbitrary ways, which may include arbitrary memory corruption, if a program receives inputs that would cause execution to get stuck in a side-effect free loop. The net effect is that the only way to ensure that programs behave in tolerably-useless fashion when given invalid input is to write them in such a fashion as to block any otherwise-useful optimizations the rules would have allowed.
The wankers who created these new C standards really love underscore characters.
LMAO
Wankers? The only wanker is the one who didn't pay attention to t he video and understand why. As he already pointed out, many people had ALREADY created their own macros using words like "bool" and "uint8" etc, so they needed to use keywords which were different and underscores like "_Bool" etc... were the answer.
...and usually the linker adds some more lol
His enunciation of "Stood-C" slightly bothers me. If it's not an initialism, I believe you should enunciate the letters by themselves.
It's a carry-over from C++ land where enunciating "ess-tee-dee" before everything from the standard library is clunky and extremely repetitive, so most people shorten it to something resembling "stood" or "stud".
WHAT WOULD IMPROVE THE C PROGRAMMING LANGUAGE IS THE ADDITION OF GRAPHICS, AUDIO, AND DEVICE FUNCTIONS.
AHAHAHA LOL
And a garbage collector, am I right? /s
#include or use GLFW or SDL or FreeGLUT to use OpenGL and BOOM you have access to the GPU. #include I think and BOOM you have audio. Well, you need first to install the correspoding libraries and link everything in the linker, but I doubt you know what I am talking about. Go back to Ruby, Javascript or whatever inefficient and crappy language you like.
@@cutemath8225 I'm pretty sure he was joking.
@@nexusclarum8000 guess it's a r/woooosh/ then
C is fast as it is for a reason.
Why are they ruining C with these mindless, stupid mods??? Guys, go home and find a hobby.
first 5 minutes, is exactly why this language should just fade out, everything rust does, does better than c or that forsaken abomination called cpp
rust doesnt even have a stable abi