Extern is good when making a program with multiple languages(Assembly and C). You have a function in assembly and you want to use it in the c code. Edit: Wow, thanks for this many likes, didn't expect that
The "extern" keyword is necessary when you need to access a global variable defined in a different translational unit. You rarely need this feature since having global variables is considered an anti-pattern in the first place. However, there are a few cases where you cannot avoid it: I once worked on a simple hobbyist operating system written in C. The keyboard buffer was updated by an interrupt rutine whenever a key was pressed and released. In order for other parts of the operating system to read which keys were pressed, I had to refer to the keyboard buffer and the number of keystrokes in the buffer, as a "extern" variables.
// With/without extern communicates different intent: int forwardDeclaration; extern int externDeclaration; // Also, to declare an item without storage space (type void), you need extern: extern void end; // ok void end; // error // Example: const void etext, edata, end; int is_mem_static(const void * c) { return c < &end; } void if_heap_free(void * m) { if (0 == is_mem_static(m)) { free(m); } }
"register" keyword is important (especially on microcontrollers) for doing very low level stuff when you must not mess up the stack and want to avoid assembly. Like working on task schedulers.
And if something is compiler dependent, that's a problem. Compilers were "stupid" 20+ or 25+ years ago, and the "suggestion" keywords register and inline were "helpers" for the compiler, but now compilers are as smart as the best assembly programmers, because the compiler writers know how to put that knowledge into compilers (also, computers are bigger and faster, and can run much more complex compilers). Modern compilers are better off ignoring these keywords. See Matt Godbolt's videos on his Compiler Explorer to see what modern compilers do, it'll blow your mind!
@Eddie Amaya, using =! working. Could you please give a piece of ASM code generated by C with "register" to confirm that really works on FreeRtos/micrium ?
Extern is essential in a lot of cases. Many source-to-source compilers depend on autogenerated global variables, and the only way to access a global variable defined in a different object file is by declaring it as extern.
"It's going to depend on the machine you are running on" is pretty much true for the majority of C code to begin with, as that's the whole point why C even exist, it's a slightly higher level cross-platform macro assembly language, that frees you from targeting a specific machine but as a compromise makes litte guarantees to be able to run on almost any machine. Also funny how you now start to worry about different behavior on different machine and just a few seconds before you said "my machine has 32 bits int and 32 bits longs then what's the point of having long instead of int" where you seem to not worry about other machines at all, since it only matters how your machine works.
Register is a hint keyword and just because the five major compilers today usually ignore it doesn't mean that any C compiler on earth will for sure ignore it. The C standard does not force the compiler to put anything into a register just because you use register, the same way it won't force a compile to inline a function just because you use inline. But it's untrue that this keyword never has an effect in major compilers. E.g. there are three situations where GCC will generate different code when you use the register keyword: - When used as part of the register variable extension. - When -O0 is in use, the compiler allocates distinct stack memory for all variables that do not have the register storage-class specifier; if register is specified, the variable may have a shorter lifespan than the code would indicate and may never be placed in memory. - On some rare x86 targets, setjmp doesn’t save the registers in all circumstances. In those cases, GCC doesn’t allocate any variables in registers unless they are marked register. So this keyword does have an effect even in GCC and it may have a larger effect in other compilers depending on certain situations.
It'd've been a good idea to separate hints from stuff that's "linguistically" necessary, like "extern", from the beginning. Many compilers use double underscore-stuff for that, anyway.
So here's a bit of a summary about the keyword `auto` prior to C23: ---------- What is the use of `auto` in C? ---------- `auto` explicitly specifies that a variable declared at block scope shall have an automatic storage duration. This means that the storage is AUTOmatically allocated when entering the block where the variable was declared and is AUTOmatically deallocated when exiting from the block (by reaching the end or by a `return`, `break` or `goto` statement). A variable declared inside a function (local variable) has an automatic storage duration by default. ---------- Why is `auto` not commonly used in C? ---------- Variables with automatic storage duration only exist inside a function. Since local variables have automatic storage duration by default, `auto` serves little to no purpose at all. int x; is basically the same as auto int x; given that `x` is a function-local variable. ---------- Why did `auto` exist in the first place? ---------- `auto` is historical. It is a remnant of C's predecessor, the B language. It existed for backwards compatibility with B code. ---------- Is there any reason to use `auto` in a modern C code? ---------- Practically, there is none. This keyword is probably only used by those who are very pedantic and want to clearly (and unnecessarily) state that their variables have an automatic storage duration. ---------- What does `auto` do in C++? ---------- C++ `auto` used to work the same way as in C, until C++11, where it was given a new meaning. Since C++11, it is now used for automatic type deduction. It can be used to specify the type of a variable or the return type of a function based on the return type of an expression (sometimes in combination with `decltype`). `auto` type deduction does not make anything dynamically typed. It is done at compile-time. Once the type is deduced, it cannot be changed. ---------- What does `auto` do in C23? ---------- Starting from C23, `auto` can now be used for automatic type deduction of variables, similar to C++11 `auto`. ---------- B Language: www.bell-labs.com/usr/dmr/www/kbman.html C auto: en.cppreference.com/w/c/keyword/auto C++ auto: en.cppreference.com/w/cpp/keyword/auto
That's why I like that Rust does not use types like int or float. Instead you type i32, u64, u8, i128, f32, f64. Also, there is usize/isize for getting the most efficient indexing type for your platform.
For C it's good practice nowadays to use stdint fixed width types for integers, so you can get similar guarantees. size_t may or may not be optimized per architecture like usize. (disclaimer: I also love Rust, these are just lessons I've taken and applied to C).
I heard that, historically, int is meant to be assigned the most efficient integer size of the platform it's being compiled for. Which is why in a "for" loop from 0 to 9, I allow myself to use int and not uint8_t, especially if "i" is getting cast in the loop anyways
You need the extern keyword on a variable to tell it that you are declaring it and not defining it, and that the actual definition is in some other file. You can get away with it by omitting the extern keyword but it will be confusing since it may read as being defined, and without any assignment the reader will assume it is initialized to zero. The compiler will catch it anyway if you defined the variable more than once. But that's a rather painful reminder. When that happens, you usually just add the keyword to remind you that it is just declared and that the actual definition is somewhere else.
Something like "Multiple definitions" will be thrown if one tries to omit the extern keyword with variables, because the compiler will try to define it implicitly. To avoid it we need to put "extern" anyway. Or am I wrong? In case of variables extern is essential because it separates declaration from definition, by default the compiler does the both and it leads to linkage problems.
@@SergeySergey-k4s you're right and there are a whole lot of ignoramuses making videos and commenting on videos. i had to stop when he said that. K&R's book is probably the shortest language definition you'll find and he hasn't even read that. pathetic.
7:34 You weren't alive at this time... I was, and was C developer. And yes _register_ saved my life when I refactored an image format converter, resampling, with big/little endian switching, my project went from 10 minutes processing by document, to about less than one minute, using _register_ . You just have to use it efficiently, because defining every single one as a register will bring nothing, while using it on the right variables (in loops), will be stupendous. But I guess that today, with caches we don't really need it anymore if our code is well written.
I believe compilers also have gotten to the point where that will most likely be optimized for you, so the compiler will figure out whether something should be in a register or not.
YOU THINK YOU ARE BETTER THAN HIM???? OR ANY OF US JUST BECAUSE YOU WERE BORN EARLIER??? IDIOTS LIKE YOU ALL OVER THE PLACE SAYING "IN MY TIME...", NOBODY CARES THAT YOU WERE LUCKY TO LIVE THOSE MOMENTS WE WANT OUR MOMENTS TOO YOU KNOW!!
@@sbypasser819 No need to shout. This was just a bit of history lesson, not some "put down your teacher babble". On the other hand, you were just rambling with Caps Lock.
@@Alche_mist its not history lesson, dont you know these people think EXACTLY that they put stg on the table because they were born earlier and we are stuck in this rotten modern world with android 11 if our old device dies
I'm pretty new to C, but I was reading up today on how the "inline" keyword can tank performance if the function doesn't fit in the L1 cache, so how it's better to just leave it up to the compiler to inline functions when it wants to.
This is true, but inline is also just a suggestion to the compiler (unless you use "__attribute__((always_inline))"). If the optimizer heuristics deems it suboptimal, it will ignore you, the coder, completely :)
@@sanderbos4243 To further elaborate, the compiler won't automatically inline functions in a different translation unit unless you are using lto (iirc, correct me if I am wrong). When you inline in C you can either use static inline, just inline. With static inline, the compiler will see if the function should be inlined, in which case it would be inlined. If it cannot, then the compiler uses a static declaration of that function instead. With just an inline it's the same except the compiler uses a external declaration of the function (in which case you have to define the non-inline version of the function seperately). In general, you should just use static inline.
I use inline mostly to avoid having to create an implementation file for simple functions: hust define them in the header and mark them as inline, no linking problem and you're done.
Thank you for this quite interesting episode. Beyond 'auto' and 'register', I like to mention the unary plus-operator. Though it is no keyword, you can write some wierd expressions like +5 *+ + +9 which is actually 45.
@@adam051838 it's like unary minus that negates a number, but the plus doesn't negate it, so "+4" is the same as "4". In the example it's just 5*9=45. It may do some stuff that would've happened if a variable was to be used in any numerical expression, like update the type or give type error, but is generally useless.
The register key word can be used with the inline asm keyword to specify a specific register for the variable (register int foo asm("eax") = 5;). This might be useful in systems programming.
Can you define multiple different versions is the variable for different architectures like an arm foo and x64 foo or do you have to pick one and stick with it
@@jlewwis1995 I mean you can't do it directly but you can use preprocessor ifdefs to test __x86_64__ or __arm__ to compile a variable declaration with arm registers or a variable declaration with x64 registers but these are compiler specific and probably not the best to use. Up to you.
The point of long double is not to get 128 bits but to get "as much floating point precession as possible" and if your system only has double precision, then long double is the same as double, so you do not lose anything by using it, you still got maximum precession possible.
Unless you are willing to use __asm__ which is not very fun nor elegant you'll need to use `register` for some low-level programs where the stack itself must be preserved, but the most effective aspect of this keyword is that like a register should work you cannot access its memory address, so if you want to make sure that during the entire program no (unknown) change to the variable has taken place you could declare it as a register thus any call to it by address won't compile. And anyways the reason nothing changes in the compiler when adding the register keyword is that some compiler straight up ignores this flag. Again, in some cases the compiler will ignore this keyword, but it is still a powerful check for a call by address.
When I write __asm__ the compiler doesn't ignore that. Also, you can write machine code instruction as a char array and then cast that char array to a function and call that function. However, you will have to write machine code this way.
I have never used int or unsigned int, I have always used int32_t or uint32_t, in fact, I don't feel comfortable when I see int in the code because that makes me stop and think for a second where the code is going to run. But if you use uint32_t then it will always be a 32 bit unsigned integer whether on an 8-bit micro or my 64bit Linux laptop.
It will always be 32 bit, but only if it compiles! It is not guaranteed to do so according to the standard, and there are platforms with word sizes not a multiple of 8 bits. Use a plain int if you don't want to consider where the code is going to run and you know you are in a smaller numeric domain anyway (say less than 10k), long if less than 2 million, or otherwise you can consider one of the least*_t or fast*_t types according to your needs to get the best choice on each platform.
@@benhetland576 While this is a fair point, odd word length architectures are very specialized. You will already know if you need to worry about this beforehand. More important imo is knowing that the exact (u)intN_t types are required to be that exact length and won't exist if it isn't possible, e.g. a CPU that only supports 32-bit words as variables can't have uint8_t, but it will have a 32-bit uint_least8_t. stdint is, well, standard, and you shouldn't be afraid to use it.
@@lazula Yes, I agree with you. The header is standard since C99, and the fact that something then doesn't compile should be a good indicator that something needs to be addressed specifically on that platform. Note that if one also needs support for an early C++ compiler (likely?) these types were not supported until several years later (2011?).
In one of my practice test in university, they gave me 80/100 even though I got all output correctly. Turned out they also evaluate the code by hand, and every of my uint32_t was marked as "not suitable". They wanted me to use 'unsigned'. Yes, not 'unsigned int', just a single 'unsigned' keyword.
@@sarahkatherine8458 The unsigned keyword doesn't really indicate a type in itself. Along with short and long these are _type modifiers_ for another base type, so unsigned alone is merely a shorthand for writing unsigned int, long a shorthand for long int, unsigned long for unsigned long int, etc. I suppose the "non-suitability" would have meant the potentially reduced portability of such a choice. A plain 'unsigned' vs the full 'unsigned int' is simply hyper-pedantry IMHO, and could be grounds for a formal complaint.
The only true useless keyword in this video is "auto", all other keywords do have a meaning, even though register is rarely ever needed, but even in GCC there are three cases where different code is generated when you use register (see my other comment about that). Yet the video did not even explain why auto even exists in C, so let me help you out: The only reason why the keyword auto is found in C is for backward compatibility. Before there was C, there was a language named B. In B there were no variable types as int was the only variable type available but there was no int keyword to declare ints. Instead at the beginning of a function there were two declaration lists of variables used by the function (other than function parameters) and those were external ones ("extrn") and local ones ("auto"). So to declare two ints you'd write "auto a, b;". Does that syntax look familiar to you? Early C was supposed to be backward compatible to B as much as possible, making porting existing code as easy as possible, so C would require an auto keyword as well. And just as in B int was chosen as the default variable type in C, so instead of "int a;" you could write "auto a;" to declare an int variable. The inheritance of B is also why C used to assume an int return type, if no return type was given for a function. So the keyword was never intended to be use for new code written in C, only for B code converted to C. C also in introduced types, so variables could now be declared by starting with a type and that made auto superfluous, so it was declared that all variables declared by type are always "auto" unless something else is declared. And since old C code used that keyword and C wanted to remain backward compatibility with itself, the keyword was kept and never removed.
As well as the 'global variable' usage, 'extern' is also used with inline functions when you need to take it's address for either: a) passing as a function pointer b) linking in the event that the compiler decides to *not* inline for some reason Pre-C23, 'auto' easily won the 'most-useless' keyword in ISO C as the only time it wasn't redundant, you could instead just write 'int' explicitly (a whole character less!) It was really only there to support some code written pre-standard. C23 adds the C++-style use though, which actually makes it quite useful now.
I've only watched until 1:29 and haven't seen the comments. My guess is the register keyword, because compilers have become so much better than they used to be, so I don't consider it that useful anymore. Edit: I didn't even know C had an auto keyword. TIL.
“Register” was much more useful back in the days before x86, when 8-bit and 16-bit CPUs were for most computers & not just limited things like embedded microprocessors. I wasn’t there for it, but my dad and granddad were also systems programmers and used C when it was brand new. Apparently it was very useful in the days of K&R C when POSIX was also emerging as a cross platform systems API spec, and most machines had very limited RAM with potentially wildly varying instruction sets (e.g. all sorts of weird address sizes, etc.). Register was useful for keeping your stack size below a quite low hard cutoff, and for passing data between C and raw assembly (which you still often needed to write OS-specific or resource intensive code in those days). Back then every company making computers had its own hardware, OS, compilers, etc. so you couldn’t just assume that the tools would correctly figure out what variables should go in registers; early GCC might do it right without the keyword, but Solaris or Deck might not.
I personally think register is the most useless, I personally never used it and as far as I know the compiler will ultimately decide whether a value belongs in a register or not if I understand correctly.
If you think of the "register" keyword as actually meaning "addressless" then it becomes a lot more interesting and useful for modern applications. It allows the compiler to perform safety checks in your code much like "const" allows for.
@@birenpatel7652 That is actually one of the benefits of the keyword. You cannot take the address of such a variable, which may (on a rare occasion these days) help the compiler take that decision to dedicate a register for it.
"My machine has 32 bits ints and 32 bits longs" - so you are writing C code exclusively for your machine that never needs to run anywhere else? Interesting, as that's actually not what pretty much the rest of all programmers out there do most of the time. Sure, you can use the newer standard ints but keep in mind that exact width types are optional. The C standard does not require int32_t to even exist. It only requires int_least32_t and int_fast32_t to exist but those are not guaranteed to be 32 bit, only to be at least 32 bit. And guess what else is guaranteed to be at least 32 bit? long.
Auto comes from B where there were no types and only auto/extern I suggest reading the C89 rationale and the bell labs reference manual if you are interested
As far as I'm aware, auto only exists _because_ of B, the language preceding C. In B, there's only 1 type, the computer word, or we'd just say "an int". You'd need to use auto in order to specify that the variable wasn't an extern (extrn). That's it, really. When C came around, it needed a much better system to represent this new PDP-11 character-thingie, so they decided to add types. Since, predominantly, variables are local, they decided to make "extern" the optional word and implicitly specify "auto". So why does C have "auto"? Because B had it. C compilers will, in fact, automatically assume the type as "int" (1 computer word) if you specify auto, but no type, just like B did.
i can imagine a future in which c++ didn't exist and the C commitee decided to implement RAII with a special keyword 'heap', and then you'd have auto (local stack frame), static (always alive) and heap (RAII) as keywords. i feel as if auto was made to have something to contrast with 'static'.
the heap has nothing to do with RAII RAII is objects being constructed when they are declared and destroyed at the end of the scope RAIIs goal is to make sure that you can never have an object in an invalid state
Just to clarify, there *is* a couple of RAII types that deal with the heap, namely unique_ptr and shared_ptr. But RAII is for any kind of resources, like file handles, mutex, db connections or anything that has to be closed if you open it.
@@kebien6020 There is nothing in unique_ptr or shared_ptr that forces the heap. You can pre-allocate the memory with alloca() or similar techniques and provide a null deleter.
There's a case not that rare that you may need to use extern (I needed in an exam and took some time to realize it). If you work with forks and events, you are mostly obliged to use some global variables to communicate between them. And if you use multiple files in a single program, then you must declare those variables as extern or otherwise there will be declared "twice". Imagine you have a main file, a functions one and a header file: you declare your global variables in the header as extern, and then include the header inside both your main file and your functions one. If extern is not used, the variables will be seen as twice declared and everything collapse.
There is nothing wrong with a variable being declared twice, the problem appears when they are defined twice (multiple definitions error). Extern helps to avoid it
Hmm, I'd probably guess "signed" since I can't off the top of my head think of an instance where it isn't implied if omitted (I just checked, and according to reference at least since C11 there is always _a_ default implication; for integers it is always signed, for char it depends on the architecture but casting a char to unsigned char and back does, by definition, result in the original value). So yeah, I'd say signed is functionally obsolete bar the edge cases of weird cross-compiling.
extern: quite useful for importing code from other languages (wasm in particular comes to mind) long: honestly just group long, int, short, float, and double together, they're all unsized register: kinda useful if you have a microcontroller, but mostly useless auto: practically useless, it's literally the default behavior for variables
I code a master source module that analyzes the command line parameters, and then the functions therein call functions residing in other source files. Extern serves a purpose. The GCC compiler does not like any variable to be used before being set, and it will complain if that happens (compiler flag for -Wall) . But, when the variable is set in one source and used in a second source, the compiler has to know to not complain. A complain message also sets up a returncode for the Make utility, which generally ceases to continue. Extern tells the compiler and also the linker, that it should not complain about "use before set", and also, to check for missing match. If the global variable is declared without extern, it is considered as locally global, to this source file only, By the way, I test my code with gcc and with clang, two excellent compilers.
The only reserved word you mention is extern. I use it when I want to link C++ function to my C program. Is there another way that You know an I don’t know?
What about signed? I have never ever seen it in use. Integers are signed by default. But also int might be useless, because you could write just signed instead. All others like unsigned long int would just be unsigned long etc...
@@leduyquang753 Right. here is the quote from the standard draft if anyone is interested: "The three types char, signed char, and unsigned char are collectively called the character types. The implementation shall define char to have the same range, representation, and behavior as either signed char or unsigned char"
I agree that "auto" is useless but register should still be given some importance because one should always prepare for the worst compiler, one with almost no optimization. Your videos are great, by the way!
Some people say compilers doing smarter than programmers. The thing is, sometimes we don't want the compiler to optimize our code, specifically for safety related industry. For every piece of code that we wrote, it can be peer reviewed so we know how it really works, maybe not best in the performance, but it is safe. With compiler optimization, how do you known it always 100% correct? Some useful code maybe optimized out!
Most useless: register, inline, auto Almost useless: signed (since ints are signed by default, but the signedness of char is implementation defined, so it's not entirely useless), while (since it's kind of a special case of the for loop) Oh, and you have unsigned in your list twice, which probably explains why you said "34" but then thought you miscounted :P
@@marusdod3685 I was pretty sure that inline was just a compiler hint, and that static was used to specify that a function does not have external linkage, but it appears the answer is more complicated than this according to this Stack Overflow post: what-is-the-use-of-the-inline-keyword-in-c . In any case, I think you're probably still wrong. If you look at the STB repo (which contains header-only libraries), I think you'll see that the inline keyword is only used for C++.
"extern" is still so useful, the reason: serves for declaring global variables but not to define them, without it, you define and declare them. Nowadays without extern would be impossible to use: "stdin" and "stdout" globals variable, which are share to any source file that includes "stdio.h"
I do agree that the long keyword somehow is useless and obfuscates the code and portability a bit. However, the size of an int ist most of the time the size of a word on the given system. So when you want a for loop to run 200 times, an int would just increment and compare if it is above 200. However if you specify a 32 bit int on a 64 bit system, it would increment, clear the upper 32 bits and compare. The other way around if you explicitly specify a 64 bit integer on a 32bit system, it would add the overflow (which is always 0) to a second 32 bit variable. So what I am trying to say: When you need highly performant code and you need it to be portable with similar performance, you might be best off with using the normal int type.
The stdint.h header also has fast keywords, for example int_fast32_t which will provide the fastest signed integer type with a width of atleast 32 bits.
Before watching the video, "register" was my guess. Modern compiler families ignore the keyword entirely. Back in late 90s the Metrowerks compiler was actually super sensitive to it, avoiding the usage of most registers on RISC architectures unless you sprinkled some of this keyword. Even if there was full automatic register allocation, before SSA optimisations became common, it was often first come first serve so yeah that wasn't ideal.
There's an old FAQ that I still goto when I need a refresher on C language constructs: c-faq (with the usual top-level domain suffix, which I can't type out because RUclips will flag it as spam) Question 1.12 talks about the "auto" keyword.
Extern is common as a keybaord. You use it every time you need to define a global varibale in an header file such that it can be accessed from other C files! I mean it's one of the most common keyword that I use...
`restrict` and `goto` come to mind. Probably `restrict` because `goto` can be used for error handling and breaking out of multiple loops. `restrict` is so useless it didn't even make it into C++! EDIT && SPOILER ALERT: Wow! I didn't know about either of those! Still surprised you didn't mention `restrict`, but it's extremely useful when compared to `auto`!
"restrict" can actually have significant influence on performance in some cases, which is why many C++ compilers have added "__restrict__" or "__restrict". Let's imagine that you have a function that is passed two pointers of the same type. In principle, the pointers could be pointing to the same piece of data, and to preserve correctness, the compiler needs to make sure to check whether they're the same, make sure any accesses get flushed out to memory, etc. This can get very expensive, especially if the pointers happen to be pointing to tree-like or graph-like data structures where aliasing could potentially occur at any node. Now, before you say "Ah! But compilers are good at optimizing!", consider what happens if these pointers are data-driven, pointing to data pulled in from a database, or maybe you're writing a library and these pointers hold parameters provided by some program using your code that hasn't even been written yet. That's right, the compiler often has absolutely no way of knowing whether the pointers could alias or not. But the programmer often knows. There are plenty of cases where the programmer can say "Yes, I know both parameters are float*'s, but one is an array of pressure values, and the other is temperatures. If they're aliasing, then something has gone very wrong somewhere else, so don't waste time checking and run my simulation faster."
As a militant 'no branching' coder, I believe the most useless keyword is 'if'. More seriously, 'extern' is as much a hint to the linker as the compiler, and very useful when your C module is interacting with library modules written in some other language than C. And 'register' does useful work in many embedded systems. So I would have to go with 'auto' as the least-useful keyword. And I'm afraid we're stuck with 'long' forever, as it's in millions of lines of legacy code still in production.
Re integer type sizes * The types in stdint.h are not mandatory. * Only the relative sizes are dictated, that is sizeof(long) >= sizeof(int) >= sizeof(short) >= sizeof(char). * A char variable has CHAR_BIT bits in it. CHAR_BIT must be at least 8, but it might be larger. Re how many bits a long double has, its sizeof(long double) * CHAR_BIT. You can static assert on it having sufficient number of bits. Note that today processors might give you more than you've asked for, e.g. a C compiler might translate floats to 32 bit floats, then the processor would auto extend them internally to perform multiplications, division, etc, then shorten the result back at the end.
Extern is NOT useless. It's how you interface with variables in other languages. Also, I personally consider it good practice to only expose the public interface to a library within a header file and use extern to import the protected members that the average user shouldn't be messing with.
The default storage class in C is "auto", which makes it unnecessary to type. When you need to reference a variable in a separately compiled module (*.o file), you need to use "extern". Functions don't require it, and your public interface ideally shouldn't expose variables like this. But proper use of extern *inside the modules themselves* allows you reduce the coupling between your modules and do things like creating tables and fallback chains for drivers. It's essentially like importing the protected members of a module. The "static" storage class limits the visibility of a symbol to the current scope, essentially marking something as private. I sometimes define short functions in my header files as "static inline". This results in each module getting it's own copy of the function, which is pretty much guaranteed to be inlined by any mainstream compiler.
@@bitskit3476 I think he meant function. I have not programmed in C in the last 25 years but, back then if I wanted to link C and Pascal code (Pascal being the calling convention, not the language), I had to tell the C compiler that the compiled function I was linking with used the pascal calling convention using something like "extern "PASCAL" func(a,b, c)".
@@cquezel yeah, there's that as well. Many libraries use something similar like ``` #ifdef __cplusplus extern "C" { #endif ``` in the header files because there are several things that are legal in C but not C++.
I'm pretty sure those keywords will never go away for the simple reason of backwards compatibility. A language as old as C is going to be dragging around with it certain legacy notions because you simply cannot afford to break working legacy code. In some ways something to be happy about as they don't really cause any real discomfort for people (does anyone really want/need a variable named "register"?) but shows how long we've come in our journey.
The register keyword is not as useless as it may seem. The functionality is used for two things. First of all, the fact that godbolt spits out register movement instead of RAM accesses is because the optimizer puts register there even if you don't. Without register being there in the first place, that wouldn't work. (Sure it can be done and is most likely being done not my stuffing the string in there but the functionality but the point stands) Secondly, it can be used for it's original purpose in low level computing libraries with long and complicated functions. It does make a difference when the function is complex enough that the optimizer may make mistakes, like multiple matrix multiplications for shading or similar. Sure splitting them in blocks makes it easier but the additional method calls and stack access does make a difference when something is run on a million verteces 144 times per second or more. Especially if it is older hardware that cannot profit as much from parallel computation on GPUs or if the task is another that cannot be done in parallel at all.
If you want to directly interact with your machine such as reading a specific cpu register directly, "register" is indispensable. so yes it is not useful for those not dealing with low level but for core embedded guys it is quite useful.
The register keyword is not useless. Here is a short example: #include int main(void) { register const a = 23; int *b = &a; *b=42; printf("%d ", a); } This program does not compile, but if you remove the register keyword it does compile, the output is then 42 on my PC and 23 on Termux on Android.
On microcontrollers I always use stdint, simply because I want to use as little FLASH and RAM as possible. On desktop for better or worse I stick with the regular short, int and longs.
extern: Most compilers assume that undefined functions that do have function prototype are already external. However, you cannot do this with global variables. If you declare a global variable in a header file, you need to mark it external explicitly. -- int/long/short: As you said, it's better to use the types declared in "stdint.h" to request specific types, unless you really don't care about the size. "long double" often translates to 80 bit IEEE floating-point as implemented in the FPU sections of Intel/AMD CPUs, but on other CPUs, it could be something different. -- "register" is _sometimes_ useful to trigger optimization when the compiler doesn't select a variable for optimization (which can happen if you have many local variables in the same function). Also, "auto" can be used to force the compiler to NOT select a variable for optimization. Again only relevant in functions that have many local variables. "volatile" on a local variable will similarly suppress optimizations (but it's mainly intended for global variables).
I once used a specialized C compiler for high performance custom processor that recognized Global registers. The register keyword enabled this behaviour. For extreme performance the global register enabled custom-hardware level capability. I've never used the"auto" keyword in the 200kloc of C code I've written
My take on those: extern is useful for writing 'extern "C"'. Yes, that's C++ and not C, but oftentimes you need to write C code that will stay valid in C++, and 'extern "C"' is necessary for that. stdint.h types are generally better than short, int or long, but... stdint.h defines them in terms of typedefs into, well, short, int and long. So these are necessary to implement the standard headers at least ;) There are also a ton of headers that declare short, int and long arguments and return values, and I'd argue that when you declare a variable with the intention of the variable to go in or out of such API, it's the best and most portable approach to just stick to the same declared type. Which will end up having a different size on different platforms, but that's the problem of the library you're calling, not with your code. register is sometimes useful when used together with compiler-specific extensions, to actually access a specific CPU register in low-level code, without using outright assembly. auto is completely useless.
@@ItsCOMMANDer_ Not true. Simple example: char *ptr = malloc(42); malloc() returns a void pointer. In C, that can be implicitly cast to any other pointer, in C++ that's a compile error. There are *a lot* more subtleties like that. Empty structs have a different size in C than in C++. Many C features appear in C++ very late, e.g. C99 designated initializers were only made legal C++ 20 years later (although things like that are widely supported as compiler extensions). It's not super hard to write C code that is also legal C++, there are even compiler options like -Wc++-compat in GCC that will alert you about many such subtleties. But they are there, and it's also pretty easy to stumble upon one of those.
Isn't extern required to avoid duplicate global variables? In the code base I work on we use extern regularly. I seem to remember leaving that word off one time and it resulted in a linker error complaining about duplicate symbols.
Arguing that people should use the standard integer types instead of "long" or "short" is also ridiculous. Firstly, "long" and "short" are not defined by their size. They're defined by their minimum numeric range, with intentional wiggle room for one's and two's complement integer encoding. The closest thing to a "long" in stdint is int_fast32_t, and even that isn't exact. int_fast32_t, a is guaranteed to be at least a certain size. Long is guaranteed to support at least a certain range, but there's nothing stopping a compiler from using say, a 36bit integer on some weird architecture that has nonstandard word sizes.
I was thinking 'auto' from the start of the video. I've honestly never used it. Now 'register' was in my vocabulary from first using 'C in the mid 80s.
extern is definitely not useless, although you can write function declarations without extern. the only real use is a global variable or global constant. long is definitely not useless as stdint is a new addition to C and cannot be used on some systems since it was added in C99 (so it will probably not exist in pre 1999 computers). It is a myth to say that this will make your code more "portable". auto seems to be more of a relic coming from b. maybe in very early inv development versions of C you had to use it? register can also be seen more often in old programs. These two "truly useless" keywords are really just relics from the past and exist for backwards compatibility. edit: saw another comment about how register is very much so important in microcontrollers, where paying attention to the stacks usage is important. as for long double, fair enough. but using it wont hurt your code! just because you cannot guarantee that the compiler will use higher precision floats doesn't mean its not worth using for the compilers that will. this video is honestly just kind of embarrassing. it ignores the rich history of C and just passes off all of these slightly-less-useful-as-time-passed features as "idk why that's there its useless." as well as ignoring everyone who is using C for a different purpose where these keywords are actually used and for good reason. i'm surprised you didnt bring up volatile.
When developing for the TI-84 Plus CE, you can't rely on the size of an int being 32-bits wide. The eZ80 is a weird backwards-compatible version of the Z80 processor, and the eZ80's 2-byte long registers are now 3 bytes long. The size of an int changed to 24 bits because it fits the size of the multibyte registers. There is some work being put in to add 48-byte "short long long" numbers as well. It is strongly recommended to use the *unsigned* integer types as specified in stdint.h because it is a little faster to do unsigned arithmetic on the (e)Z80. There is also a floating-point implementation, but there isn't a double-precision floating point implementation yet. The double type is an alias to a float, but a double precision floating point implementation, if made, would probably get the name long double. However, TI conveniently provides routines for 14-DIGIT precision floating point values in their OS.
Here you got a great example of how you define a word. If I talk about "portability", I talk about the possibility to compile a program across a *WIDE* range of compilers - even legacy compilers like Turbo C or Coherent C (this one only supports K&R, really!!). No fancy *stdint.h* to be found here. The only way to get an idea of what a certain core datatype actually does is to make a program with a lot of *sizeof()* calls - or consult the documentation. So in my world *long* is not quite superfluous. What I tend to do though is make my own .h file with a lot of *typedef* where I can abstract certain datatypes like 32 bit signed, 16 bit unsigned, signed characters, etc. By matching certain compiler macros with certain definitions (e.g. *__TURBOC__* or *__GCC__* ) I can make the process somewhat transparent. Disadvantage: every time you add a new compiler you've got to update the thing. I agree with *auto* and *register* though. I've been programming C for about 40 years now and I *never* used them.
My two cents: Register is important when the function has more arguments than the available temporary registers of the CPU you are using and you want to execute some loops/algorithms faster and not let the compiler to use the stack for that small code
Your example for register is useless. You need to pick closer to more real-life examples for it to make effect. For starters, you need more variables than how many what CPU registers you have, otherwise, the registers will just be used. If you have code that is not so common to be written and you know how frequently some variables are used, register can help when doing dev builds without optimizations. I'd give register a honourable mention for that reason. Not needed any more for nowadays compilers but it was crucial to extract every single bit of performance in old machines and to speed up the compiler.
For *very* old compilers only. I tried the dhrystone benchmark with and without register in the late 80s, the compiler's optimizer was better in assigning variables to registers. The register keyword blocked sensible register use, so resulted in slower code.
@@Hauketal I've used on recent compilers (clang and gcc) with code from work and it worked on about half of the tries. I hand picked the places where I thought it would make sense to send and keep a certain variable to and in the register but the compiler didn't decide such. (Do note the examples were not obvious code on how to optimize). And it worked on -O0! However, after selecting to compile in -O1 or above, the compiler picked up those places too to place in registers and it was not needed any more.
Referring to how C code is writen in Embedded Software following the Static Architecture of AutoSar (for Automotive) with the rules applied from MISRA and CERT and the rules that are also applied according to safety (ASIL level), then you would understand the importance of some keywords like: extern and why we just don't use the standard libraries.
My favorite useless keyword in C is "inline". Because these days the compiler decides on it's own if it wants to inline a function call or not, and will generally ignore your wishes on the matter. Ye olde C compilers did need you to tell them which functions should be inlined and which shouldn't, but modern optimizing compilers can figure it out on their own.
Auto and register are complementary. In the elden days, accessing variables on the stack required a lot of address calculation. If your compiler spported auto, you tell the compiler to store a variable in a register, ie. Single instruction access for a highly used variable and auto when the amount of accesses was minimal. Now a days , native addressing modes include stack plus index and stack plus frame plus index and more. The use of register also alters the size of the cpu context that must be saved for interrupts. By the way, inline is also only a hint and usually not supported. The three keywords listed here assume a compiler with very limited optimization capabilities and are not in any way portable.
Auto, register, static and extern are all storage class specifies. Auto is scoped limited to a function. Static sets scope based on its placement. Extern scope is outside of this module. I don't know if register is limited to a function scope. If so, I agree with your comment. Because auto and register exist only for one instance.
@10:15 The auto keyword is mostly useless, with exception of the fact that it allows you to omit a type specifier. In C, everything is an int by default, so this is perfectly valid code: main(argc, argv) char *argv[]; { for (auto i = 0; i < argc; i++) puts(argv[i]); }
so microcontrollers do not have a lot of these libraries and even if they did, space is a precious resource in many of them. Using a long is probably the best solution for many of them.
I find it very simple. It isn't so bloated and fast to learn every concept of C. I would even say C is the easiest language to "master", because there arent that many features to learn, let alone the standard library is very small compared to other languages. But you can shoot yourself in the foot very easy. Plus it feels like 50% of C behavior is undefined. I mean it starts vague datatype sizes defined as char
Register is mostly useless now. It still has meaning a few places, but they are few and far between. In the far mists of C pre-history, the PDP-11 had 8 integer registers. One was used for the PC, one for the stack, and one for a frame pointer. The other five were for general use by the compiler, but... r0 was used for return values (ints), and r0 and r1 were used for return values (long). That left r2, r3, and r4 available for 'register variables', and the C compiler allocated them in r4, r3, r2 order. Generally, they were a hint that these variables were used a lot and, the PDP-11 having no cache to speak of, would result in smaller and faster code if the named variables were put in registers, rather than fetching them off the stack for each use. In these days, however, the poorest common compilers do a much better job of type checking, optimizing, and scheduling code than even the fanciest lint tools of yore. And so for 99+% of code, register is meaningless. (I will point out that the example used in this clip isn't a good one: even the compilers of yore would optimize a 'register' variable associated with r2-r4 to r0 if it was returned as soon as it was computed. So I wouldn't expect that example to have made a difference in compiler output even back in the days of the dinosaurs if optimization was enabled.) Long after the PDP-11, although partly as fallout from the PDP-11, the register keyword found significant use in the x86. In PDP-11 days, all floating point arithmetic was done in 'double' precision, and results stored to variables in whatever type the variable held. It was therefore helpful (numerically) if critical values, which might be 'float', were nonetheless held in registers, where they had type 'double', and used accordingly. This avoided loss of precision, and for things like dot products, could yield significantly better numerical results. The x86 had a similar issue: in the old days (pre-SSE2) the x86 FP "stack" model holds/held internal values with 80 bits of precision, but storing a value to a memory location truncates/truncated it to 64 (double) or 32 (float) bits (according to its type). So putting critical variables into a register for the duration of a running sum or sum of products can/could significantly improve both speed and numerical accuracy.
Extern is good when making a program with multiple languages(Assembly and C). You have a function in assembly and you want to use it in the c code.
Edit: Wow, thanks for this many likes, didn't expect that
It's good to make nice global variables too (you declare it as extern in the header and initialize it in the source )
@@leokiller123able yeah it was really nice when recoding malloc.
For interop, I'd argue it is very necessary. You won't use it for Hello, World!, but for a code base with mixed languages it's how you bridge them.
Function declarations are implicitly extern though, so it doesn't help in that case. It's only ever useful for global variables
Exactly it's very useful for microprocessor programming
The "extern" keyword is necessary when you need to access a global variable defined in a different translational unit. You rarely need this feature since having global variables is considered an anti-pattern in the first place. However, there are a few cases where you cannot avoid it: I once worked on a simple hobbyist operating system written in C. The keyboard buffer was updated by an interrupt rutine whenever a key was pressed and released. In order for other parts of the operating system to read which keys were pressed, I had to refer to the keyboard buffer and the number of keystrokes in the buffer, as a "extern" variables.
I use "extern" in header files for exactly this.
Exactly this.
Yup, exactly! It’s a low level language feature. And many of us use C for its low level features!!
@@pierreabbat6157 "I use "extern" in header files for exactly this." Imagine not using extern in your header files.. :D
// With/without extern communicates different intent:
int forwardDeclaration;
extern int externDeclaration;
// Also, to declare an item without storage space (type void), you need extern:
extern void end; // ok
void end; // error
// Example:
const void etext, edata, end;
int is_mem_static(const void * c) {
return c < &end;
}
void if_heap_free(void * m) {
if (0 == is_mem_static(m)) {
free(m);
}
}
"register" keyword is important (especially on microcontrollers) for doing very low level stuff when you must not mess up the stack and want to avoid assembly. Like working on task schedulers.
That was exactly what I was about to point
"register" just recomendation for compiler, there is no 100% garanty that your variable will be located in the register...
@@EnkoVlog I think it depends on which compiler you are using
And if something is compiler dependent, that's a problem.
Compilers were "stupid" 20+ or 25+ years ago, and the "suggestion" keywords register and inline were "helpers" for the compiler, but now compilers are as smart as the best assembly programmers, because the compiler writers know how to put that knowledge into compilers (also, computers are bigger and faster, and can run much more complex compilers). Modern compilers are better off ignoring these keywords. See Matt Godbolt's videos on his Compiler Explorer to see what modern compilers do, it'll blow your mind!
@Eddie Amaya, using =! working. Could you please give a piece of ASM code generated by C with "register" to confirm that really works on FreeRtos/micrium ?
Extern is essential in a lot of cases. Many source-to-source compilers depend on autogenerated global variables, and the only way to access a global variable defined in a different object file is by declaring it as extern.
"It's going to depend on the machine you are running on" is pretty much true for the majority of C code to begin with, as that's the whole point why C even exist, it's a slightly higher level cross-platform macro assembly language, that frees you from targeting a specific machine but as a compromise makes litte guarantees to be able to run on almost any machine. Also funny how you now start to worry about different behavior on different machine and just a few seconds before you said "my machine has 32 bits int and 32 bits longs then what's the point of having long instead of int" where you seem to not worry about other machines at all, since it only matters how your machine works.
Register is a hint keyword and just because the five major compilers today usually ignore it doesn't mean that any C compiler on earth will for sure ignore it. The C standard does not force the compiler to put anything into a register just because you use register, the same way it won't force a compile to inline a function just because you use inline. But it's untrue that this keyword never has an effect in major compilers. E.g. there are three situations where GCC will generate different code when you use the register keyword:
- When used as part of the register variable extension.
- When -O0 is in use, the compiler allocates distinct stack memory for all variables that do not have the register storage-class specifier; if register is specified, the variable may have a shorter lifespan than the code would indicate and may never be placed in memory.
- On some rare x86 targets, setjmp doesn’t save the registers in all circumstances. In those cases, GCC doesn’t allocate any variables in registers unless they are marked register.
So this keyword does have an effect even in GCC and it may have a larger effect in other compilers depending on certain situations.
It'd've been a good idea to separate hints from stuff that's "linguistically" necessary, like "extern", from the beginning. Many compilers use double underscore-stuff for that, anyway.
So here's a bit of a summary about the keyword `auto` prior to C23:
----------
What is the use of `auto` in C?
----------
`auto` explicitly specifies that a variable declared at block scope shall have an automatic storage duration. This means that the storage is AUTOmatically allocated when entering the block where the variable was declared and is AUTOmatically deallocated when exiting from the block (by reaching the end or by a `return`, `break` or `goto` statement). A variable declared inside a function (local variable) has an automatic storage duration by default.
----------
Why is `auto` not commonly used in C?
----------
Variables with automatic storage duration only exist inside a function. Since local variables have automatic storage duration by default, `auto` serves little to no purpose at all.
int x;
is basically the same as
auto int x;
given that `x` is a function-local variable.
----------
Why did `auto` exist in the first place?
----------
`auto` is historical. It is a remnant of C's predecessor, the B language. It existed for backwards compatibility with B code.
----------
Is there any reason to use `auto` in a modern C code?
----------
Practically, there is none. This keyword is probably only used by those who are very pedantic and want to clearly (and unnecessarily) state that their variables have an automatic storage duration.
----------
What does `auto` do in C++?
----------
C++ `auto` used to work the same way as in C, until C++11, where it was given a new meaning.
Since C++11, it is now used for automatic type deduction. It can be used to specify the type of a variable or the return type of a function based on the return type of an expression (sometimes in combination with `decltype`).
`auto` type deduction does not make anything dynamically typed. It is done at compile-time. Once the type is deduced, it cannot be changed.
----------
What does `auto` do in C23?
----------
Starting from C23, `auto` can now be used for automatic type deduction of variables, similar to C++11 `auto`.
----------
B Language:
www.bell-labs.com/usr/dmr/www/kbman.html
C auto:
en.cppreference.com/w/c/keyword/auto
C++ auto:
en.cppreference.com/w/cpp/keyword/auto
Very useful comment
Thanks
That's actually cool, I like there's backward compatibility even if it's something from 400 years ago
The auto keyword is useless in C because it's implicit so specifying it doesn't change anything
And ironically, auto is extremely useFUL in C++ (but of course has totally different meaning).
@@MH-ri2fb It had same meaning but was deprecated for a long time then removed and replaced with new meaning.
That's why I like that Rust does not use types like int or float. Instead you type i32, u64, u8, i128, f32, f64. Also, there is usize/isize for getting the most efficient indexing type for your platform.
For C it's good practice nowadays to use stdint fixed width types for integers, so you can get similar guarantees. size_t may or may not be optimized per architecture like usize. (disclaimer: I also love Rust, these are just lessons I've taken and applied to C).
I heard that, historically, int is meant to be assigned the most efficient integer size of the platform it's being compiled for. Which is why in a "for" loop from 0 to 9, I allow myself to use int and not uint8_t, especially if "i" is getting cast in the loop anyways
@@SpeedingFlare In that case, why not use uintfast?
@@adammucci3463 interesting, I've never heard of that. I'll have to check that out!
I don't think it's necessarily the "most efficient indexing type", whatever that means, I'm pretty sure it's just a pointer sized integer.
You need the extern keyword on a variable to tell it that you are declaring it and not defining it, and that the actual definition is in some other file. You can get away with it by omitting the extern keyword but it will be confusing since it may read as being defined, and without any assignment the reader will assume it is initialized to zero. The compiler will catch it anyway if you defined the variable more than once. But that's a rather painful reminder. When that happens, you usually just add the keyword to remind you that it is just declared and that the actual definition is somewhere else.
Something like "Multiple definitions" will be thrown if one tries to omit the extern keyword with variables, because the compiler will try to define it implicitly. To avoid it we need to put "extern" anyway. Or am I wrong?
In case of variables extern is essential because it separates declaration from definition, by default the compiler does the both and it leads to linkage problems.
@@SergeySergey-k4s you're right and there are a whole lot of ignoramuses making videos and commenting on videos. i had to stop when he said that. K&R's book is probably the shortest language definition you'll find and he hasn't even read that. pathetic.
7:34 You weren't alive at this time... I was, and was C developer. And yes _register_ saved my life when I refactored an image format converter, resampling, with big/little endian switching, my project went from 10 minutes processing by document, to about less than one minute, using _register_ . You just have to use it efficiently, because defining every single one as a register will bring nothing, while using it on the right variables (in loops), will be stupendous. But I guess that today, with caches we don't really need it anymore if our code is well written.
I believe compilers also have gotten to the point where that will most likely be optimized for you, so the compiler will figure out whether something should be in a register or not.
YOU THINK YOU ARE BETTER THAN HIM???? OR ANY OF US JUST BECAUSE YOU WERE BORN EARLIER??? IDIOTS LIKE YOU ALL OVER THE PLACE SAYING "IN MY TIME...", NOBODY CARES THAT YOU WERE LUCKY TO LIVE THOSE MOMENTS WE WANT OUR MOMENTS TOO YOU KNOW!!
@@sbypasser819 No need to shout. This was just a bit of history lesson, not some "put down your teacher babble".
On the other hand, you were just rambling with Caps Lock.
@@sbypasser819 Take a chill pill
@@Alche_mist its not history lesson, dont you know these people think EXACTLY that they put stg on the table because they were born earlier and we are stuck in this rotten modern world with android 11 if our old device dies
@0:45 - no, there are 34 keywords listed, not 35 as you wrote at the top right. You have 'unsigned' in your list twice, so the total count is 34.
I'm pretty new to C, but I was reading up today on how the "inline" keyword can tank performance if the function doesn't fit in the L1 cache, so how it's better to just leave it up to the compiler to inline functions when it wants to.
Compiler developers are really really really smart people
This is true, but inline is also just a suggestion to the compiler (unless you use "__attribute__((always_inline))"). If the optimizer heuristics deems it suboptimal, it will ignore you, the coder, completely :)
@@MH-ri2fb I see, thanks!
@@sanderbos4243 To further elaborate, the compiler won't automatically inline functions in a different translation unit unless you are using lto (iirc, correct me if I am wrong). When you inline in C you can either use static inline, just inline. With static inline, the compiler will see if the function should be inlined, in which case it would be inlined. If it cannot, then the compiler uses a static declaration of that function instead. With just an inline it's the same except the compiler uses a external declaration of the function (in which case you have to define the non-inline version of the function seperately).
In general, you should just use static inline.
I use inline mostly to avoid having to create an implementation file for simple functions: hust define them in the header and mark them as inline, no linking problem and you're done.
Thank you for this quite interesting episode.
Beyond 'auto' and 'register', I like to mention the unary plus-operator. Though it is no keyword, you can write some wierd expressions like +5 *+ + +9 which is actually 45.
It's not useless, it's meant to impress your friends
what the heck even
Could you state the order of precedence here for this lowly noob?
@@adam051838 it's like unary minus that negates a number, but the plus doesn't negate it, so "+4" is the same as "4". In the example it's just 5*9=45. It may do some stuff that would've happened if a variable was to be used in any numerical expression, like update the type or give type error, but is generally useless.
Unary plus serves as a universal "decay the type of this expression". `+[]{return 42; };` will be a function pointer.
The register key word can be used with the inline asm keyword to specify a specific register for the variable (register int foo asm("eax") = 5;). This might be useful in systems programming.
Correct observation. But this is system specific and no longer C.
Can you define multiple different versions is the variable for different architectures like an arm foo and x64 foo or do you have to pick one and stick with it
@@jlewwis1995 I mean you can't do it directly but you can use preprocessor ifdefs to test __x86_64__ or __arm__ to compile a variable declaration with arm registers or a variable declaration with x64 registers but these are compiler specific and probably not the best to use. Up to you.
That’s very interesting. Thank you.
The point of long double is not to get 128 bits but to get "as much floating point precession as possible" and if your system only has double precision, then long double is the same as double, so you do not lose anything by using it, you still got maximum precession possible.
Unless you are willing to use __asm__ which is not very fun nor elegant you'll need to use `register` for some low-level programs where the stack itself must be preserved, but the most effective aspect of this keyword is that like a register should work you cannot access its memory address, so if you want to make sure that during the entire program no (unknown) change to the variable has taken place you could declare it as a register thus any call to it by address won't compile. And anyways the reason nothing changes in the compiler when adding the register keyword is that some compiler straight up ignores this flag. Again, in some cases the compiler will ignore this keyword, but it is still a powerful check for a call by address.
When I write __asm__ the compiler doesn't ignore that. Also, you can write machine code instruction as a char array and then cast that char array to a function and call that function. However, you will have to write machine code this way.
I have never used int or unsigned int, I have always used int32_t or uint32_t, in fact, I don't feel comfortable when I see int in the code because that makes me stop and think for a second where the code is going to run. But if you use uint32_t then it will always be a 32 bit unsigned integer whether on an 8-bit micro or my 64bit Linux laptop.
It will always be 32 bit, but only if it compiles! It is not guaranteed to do so according to the standard, and there are platforms with word sizes not a multiple of 8 bits. Use a plain int if you don't want to consider where the code is going to run and you know you are in a smaller numeric domain anyway (say less than 10k), long if less than 2 million, or otherwise you can consider one of the least*_t or fast*_t types according to your needs to get the best choice on each platform.
@@benhetland576 While this is a fair point, odd word length architectures are very specialized. You will already know if you need to worry about this beforehand.
More important imo is knowing that the exact (u)intN_t types are required to be that exact length and won't exist if it isn't possible, e.g. a CPU that only supports 32-bit words as variables can't have uint8_t, but it will have a 32-bit uint_least8_t.
stdint is, well, standard, and you shouldn't be afraid to use it.
@@lazula Yes, I agree with you. The header is standard since C99, and the fact that something then doesn't compile should be a good indicator that something needs to be addressed specifically on that platform. Note that if one also needs support for an early C++ compiler (likely?) these types were not supported until several years later (2011?).
In one of my practice test in university, they gave me 80/100 even though I got all output correctly. Turned out they also evaluate the code by hand, and every of my uint32_t was marked as "not suitable".
They wanted me to use 'unsigned'. Yes, not 'unsigned int', just a single 'unsigned' keyword.
@@sarahkatherine8458 The unsigned keyword doesn't really indicate a type in itself. Along with short and long these are _type modifiers_ for another base type, so unsigned alone is merely a shorthand for writing unsigned int, long a shorthand for long int, unsigned long for unsigned long int, etc. I suppose the "non-suitability" would have meant the potentially reduced portability of such a choice. A plain 'unsigned' vs the full 'unsigned int' is simply hyper-pedantry IMHO, and could be grounds for a formal complaint.
If someone is saying smthing about goto or register, remember that they are heavily used in the Unix world
Register used to be useful, but it's not really anymore
Some of those "useless" keywords are for systems-level programming and are still used today.
The number thing I actually like how Rust does it, i8 i16 i32 i64 i128 for signed integers, u8, u16, u32, u64, u128 for unsigned integers.
The only true useless keyword in this video is "auto", all other keywords do have a meaning, even though register is rarely ever needed, but even in GCC there are three cases where different code is generated when you use register (see my other comment about that). Yet the video did not even explain why auto even exists in C, so let me help you out:
The only reason why the keyword auto is found in C is for backward compatibility. Before there was C, there was a language named B. In B there were no variable types as int was the only variable type available but there was no int keyword to declare ints. Instead at the beginning of a function there were two declaration lists of variables used by the function (other than function parameters) and those were external ones ("extrn") and local ones ("auto"). So to declare two ints you'd write "auto a, b;". Does that syntax look familiar to you?
Early C was supposed to be backward compatible to B as much as possible, making porting existing code as easy as possible, so C would require an auto keyword as well. And just as in B int was chosen as the default variable type in C, so instead of "int a;" you could write "auto a;" to declare an int variable. The inheritance of B is also why C used to assume an int return type, if no return type was given for a function. So the keyword was never intended to be use for new code written in C, only for B code converted to C. C also in introduced types, so variables could now be declared by starting with a type and that made auto superfluous, so it was declared that all variables declared by type are always "auto" unless something else is declared. And since old C code used that keyword and C wanted to remain backward compatibility with itself, the keyword was kept and never removed.
As well as the 'global variable' usage, 'extern' is also used with inline functions when you need to take it's address for either:
a) passing as a function pointer
b) linking in the event that the compiler decides to *not* inline for some reason
Pre-C23, 'auto' easily won the 'most-useless' keyword in ISO C as the only time it wasn't redundant, you could instead just write 'int' explicitly (a whole character less!) It was really only there to support some code written pre-standard.
C23 adds the C++-style use though, which actually makes it quite useful now.
I've only watched until 1:29 and haven't seen the comments. My guess is the register keyword, because compilers have become so much better than they used to be, so I don't consider it that useful anymore.
Edit: I didn't even know C had an auto keyword. TIL.
“Register” was much more useful back in the days before x86, when 8-bit and 16-bit CPUs were for most computers & not just limited things like embedded microprocessors. I wasn’t there for it, but my dad and granddad were also systems programmers and used C when it was brand new.
Apparently it was very useful in the days of K&R C when POSIX was also emerging as a cross platform systems API spec, and most machines had very limited RAM with potentially wildly varying instruction sets (e.g. all sorts of weird address sizes, etc.). Register was useful for keeping your stack size below a quite low hard cutoff, and for passing data between C and raw assembly (which you still often needed to write OS-specific or resource intensive code in those days).
Back then every company making computers had its own hardware, OS, compilers, etc. so you couldn’t just assume that the tools would correctly figure out what variables should go in registers; early GCC might do it right without the keyword, but Solaris or Deck might not.
I personally think register is the most useless, I personally never used it and as far as I know the compiler will ultimately decide whether a value belongs in a register or not if I understand correctly.
If you think of the "register" keyword as actually meaning "addressless" then it becomes a lot more interesting and useful for modern applications. It allows the compiler to perform safety checks in your code much like "const" allows for.
@@birenpatel7652 That is actually one of the benefits of the keyword. You cannot take the address of such a variable, which may (on a rare occasion these days) help the compiler take that decision to dedicate a register for it.
Sometimes embedded system specific compilers treat register differently that does speed up operations.
"My machine has 32 bits ints and 32 bits longs" - so you are writing C code exclusively for your machine that never needs to run anywhere else? Interesting, as that's actually not what pretty much the rest of all programmers out there do most of the time. Sure, you can use the newer standard ints but keep in mind that exact width types are optional. The C standard does not require int32_t to even exist. It only requires int_least32_t and int_fast32_t to exist but those are not guaranteed to be 32 bit, only to be at least 32 bit. And guess what else is guaranteed to be at least 32 bit? long.
I hate "auto", with all of my existence
Auto comes from B where there were no types and only auto/extern
I suggest reading the C89 rationale and the bell labs reference manual if you are interested
That’s very interesting. Thank you.
As far as I'm aware, auto only exists _because_ of B, the language preceding C. In B, there's only 1 type, the computer word, or we'd just say "an int". You'd need to use auto in order to specify that the variable wasn't an extern (extrn). That's it, really. When C came around, it needed a much better system to represent this new PDP-11 character-thingie, so they decided to add types. Since, predominantly, variables are local, they decided to make "extern" the optional word and implicitly specify "auto".
So why does C have "auto"? Because B had it. C compilers will, in fact, automatically assume the type as "int" (1 computer word) if you specify auto, but no type, just like B did.
Interesting, I didn't even know auto existed to be honest.
i can imagine a future in which c++ didn't exist and the C commitee decided to implement RAII with a special keyword 'heap', and then you'd have auto (local stack frame), static (always alive) and heap (RAII) as keywords.
i feel as if auto was made to have something to contrast with 'static'.
the heap has nothing to do with RAII
RAII is objects being constructed when they are declared and destroyed at the end of the scope
RAIIs goal is to make sure that you can never have an object in an invalid state
Just to clarify, there *is* a couple of RAII types that deal with the heap, namely unique_ptr and shared_ptr. But RAII is for any kind of resources, like file handles, mutex, db connections or anything that has to be closed if you open it.
@@kebien6020 There is nothing in unique_ptr or shared_ptr that forces the heap. You can pre-allocate the memory with alloca() or similar techniques and provide a null deleter.
There's a case not that rare that you may need to use extern (I needed in an exam and took some time to realize it). If you work with forks and events, you are mostly obliged to use some global variables to communicate between them. And if you use multiple files in a single program, then you must declare those variables as extern or otherwise there will be declared "twice". Imagine you have a main file, a functions one and a header file: you declare your global variables in the header as extern, and then include the header inside both your main file and your functions one. If extern is not used, the variables will be seen as twice declared and everything collapse.
There is nothing wrong with a variable being declared twice, the problem appears when they are defined twice (multiple definitions error). Extern helps to avoid it
Hmm, I'd probably guess "signed" since I can't off the top of my head think of an instance where it isn't implied if omitted (I just checked, and according to reference at least since C11 there is always _a_ default implication; for integers it is always signed, for char it depends on the architecture but casting a char to unsigned char and back does, by definition, result in the original value). So yeah, I'd say signed is functionally obsolete bar the edge cases of weird cross-compiling.
extern: quite useful for importing code from other languages (wasm in particular comes to mind)
long: honestly just group long, int, short, float, and double together, they're all unsized
register: kinda useful if you have a microcontroller, but mostly useless
auto: practically useless, it's literally the default behavior for variables
I code a master source module that analyzes the command line parameters, and then the functions therein call functions residing in other source files. Extern serves a purpose.
The GCC compiler does not like any variable to be used before being set, and it will complain if that happens (compiler flag for -Wall) . But, when the variable is set in one source and used in a second source, the compiler has to know to not complain. A complain message also sets up a returncode for the Make utility, which generally ceases to continue.
Extern tells the compiler and also the linker, that it should not complain about "use before set", and also, to check for missing match. If the global variable is declared without extern, it is considered as locally global, to this source file only,
By the way, I test my code with gcc and with clang, two excellent compilers.
0:47 Actually, you counted correctly, it’s just that “unsigned” is repeated.
The most useless is… “if”, obviously. Who wants conditionals anyways?
I haven't written C code in about 20 years and this still amused me.
The only reserved word you mention is extern. I use it when I want to link C++ function to my C program. Is there another way that You know an I don’t know?
inline - even compiler ignores it :)
What about signed? I have never ever seen it in use. Integers are signed by default.
But also int might be useless, because you could write just signed instead. All others like unsigned long int would just be unsigned long etc...
char can be signed or unsigned.
@@Brock-Landers It's not, the implementation can make it either signed or unsigned.
@@Brock-Landers char isnt signed “by default”, it depends on the architecture.
@@leduyquang753 Right. here is the quote from the standard draft if anyone is interested: "The three types char, signed char, and unsigned char are collectively called the character types. The implementation shall define char to have the same range, representation, and behavior as either signed char or unsigned char"
Signed is useful if you need to cast an unsigned int to a signed int.
I can't think of any other uses.
I agree that "auto" is useless but register should still be given some importance because one should always prepare for the worst compiler, one with almost no optimization. Your videos are great, by the way!
Auto is nice in C++, should of used it in Java and C# over var.
Too much idle chatter.
I can't believe you didn't talk about `inline` here!
'auto' is genuinely superfluous; the others may be unnecessary with some compilers or linkers but definitely required with others.
Yup. Nowadays, "register" just means "you can't take the address of this".
I despise "const". Don't tell me what I can and can't do! 😅
Some people say compilers doing smarter than programmers. The thing is, sometimes we don't want the compiler to optimize our code, specifically for safety related industry. For every piece of code that we wrote, it can be peer reviewed so we know how it really works, maybe not best in the performance, but it is safe. With compiler optimization, how do you known it always 100% correct? Some useful code maybe optimized out!
Most useless: register, inline, auto
Almost useless: signed (since ints are signed by default, but the signedness of char is implementation defined, so it's not entirely useless), while (since it's kind of a special case of the for loop)
Oh, and you have unsigned in your list twice, which probably explains why you said "34" but then thought you miscounted :P
inline isn't useless at all. it allows you to declare functions in the header files and ship header only libraries
@@marusdod3685 I was pretty sure that inline was just a compiler hint, and that static was used to specify that a function does not have external linkage, but it appears the answer is more complicated than this according to this Stack Overflow post: what-is-the-use-of-the-inline-keyword-in-c .
In any case, I think you're probably still wrong. If you look at the STB repo (which contains header-only libraries), I think you'll see that the inline keyword is only used for C++.
@@paulk314 Inline keyword also reduces the size of binaries, because inline functions are not generated if they are not used.
@@patryk_49 the compiler decides whether to inline a function. The keyword just acts as a hint, not a requirement.
replace while with do...while. whenever I see someone use do...while I always ask them why? What are they thinking to use do...while?
"extern" is still so useful, the reason: serves for declaring global variables but not to define them, without it, you define and declare them. Nowadays without extern would be impossible to use: "stdin" and "stdout" globals variable, which are share to any source file that includes "stdio.h"
I do agree that the long keyword somehow is useless and obfuscates the code and portability a bit. However, the size of an int ist most of the time the size of a word on the given system. So when you want a for loop to run 200 times, an int would just increment and compare if it is above 200. However if you specify a 32 bit int on a 64 bit system, it would increment, clear the upper 32 bits and compare. The other way around if you explicitly specify a 64 bit integer on a 32bit system, it would add the overflow (which is always 0) to a second 32 bit variable.
So what I am trying to say: When you need highly performant code and you need it to be portable with similar performance, you might be best off with using the normal int type.
The stdint.h header also has fast keywords, for example int_fast32_t which will provide the fastest signed integer type with a width of atleast 32 bits.
@@MH-xb3kp Ohh thanks, good to know. Didn't know that
Before watching the video, "register" was my guess. Modern compiler families ignore the keyword entirely. Back in late 90s the Metrowerks compiler was actually super sensitive to it, avoiding the usage of most registers on RISC architectures unless you sprinkled some of this keyword. Even if there was full automatic register allocation, before SSA optimisations became common, it was often first come first serve so yeah that wasn't ideal.
There's an old FAQ that I still goto when I need a refresher on C language constructs: c-faq (with the usual top-level domain suffix, which I can't type out because RUclips will flag it as spam)
Question 1.12 talks about the "auto" keyword.
Extern is common as a keybaord. You use it every time you need to define a global varibale in an header file such that it can be accessed from other C files!
I mean it's one of the most common keyword that I use...
`restrict` and `goto` come to mind. Probably `restrict` because `goto` can be used for error handling and breaking out of multiple loops. `restrict` is so useless it didn't even make it into C++!
EDIT && SPOILER ALERT:
Wow! I didn't know about either of those! Still surprised you didn't mention `restrict`, but it's extremely useful when compared to `auto`!
"restrict" can actually have significant influence on performance in some cases, which is why many C++ compilers have added "__restrict__" or "__restrict".
Let's imagine that you have a function that is passed two pointers of the same type. In principle, the pointers could be pointing to the same piece of data, and to preserve correctness, the compiler needs to make sure to check whether they're the same, make sure any accesses get flushed out to memory, etc. This can get very expensive, especially if the pointers happen to be pointing to tree-like or graph-like data structures where aliasing could potentially occur at any node.
Now, before you say "Ah! But compilers are good at optimizing!", consider what happens if these pointers are data-driven, pointing to data pulled in from a database, or maybe you're writing a library and these pointers hold parameters provided by some program using your code that hasn't even been written yet. That's right, the compiler often has absolutely no way of knowing whether the pointers could alias or not.
But the programmer often knows. There are plenty of cases where the programmer can say "Yes, I know both parameters are float*'s, but one is an array of pressure values, and the other is temperatures. If they're aliasing, then something has gone very wrong somewhere else, so don't waste time checking and run my simulation faster."
As a militant 'no branching' coder, I believe the most useless keyword is 'if'.
More seriously, 'extern' is as much a hint to the linker as the compiler, and very useful when your C module is interacting with library modules written in some other language than C.
And 'register' does useful work in many embedded systems. So I would have to go with 'auto' as the least-useful keyword.
And I'm afraid we're stuck with 'long' forever, as it's in millions of lines of legacy code still in production.
0:56 Must be 34 keywords then.
Bcs you have unsigned two times in the list
Re integer type sizes
* The types in stdint.h are not mandatory.
* Only the relative sizes are dictated, that is sizeof(long) >= sizeof(int) >= sizeof(short) >= sizeof(char).
* A char variable has CHAR_BIT bits in it. CHAR_BIT must be at least 8, but it might be larger.
Re how many bits a long double has, its sizeof(long double) * CHAR_BIT. You can static assert on it having sufficient number of bits. Note that today processors might give you more than you've asked for, e.g. a C compiler might translate floats to 32 bit floats, then the processor would auto extend them internally to perform multiplications, division, etc, then shorten the result back at the end.
And yeah, count me in with the dinosaurs. Once upon a time when I use to write C code for the Amiga, "register" did actually speed things up.
Extern is NOT useless. It's how you interface with variables in other languages. Also, I personally consider it good practice to only expose the public interface to a library within a header file and use extern to import the protected members that the average user shouldn't be messing with.
The default storage class in C is "auto", which makes it unnecessary to type.
When you need to reference a variable in a separately compiled module (*.o file), you need to use "extern". Functions don't require it, and your public interface ideally shouldn't expose variables like this. But proper use of extern *inside the modules themselves* allows you reduce the coupling between your modules and do things like creating tables and fallback chains for drivers. It's essentially like importing the protected members of a module.
The "static" storage class limits the visibility of a symbol to the current scope, essentially marking something as private. I sometimes define short functions in my header files as "static inline". This results in each module getting it's own copy of the function, which is pretty much guaranteed to be inlined by any mainstream compiler.
@@bitskit3476 I think he meant function. I have not programmed in C in the last 25 years but, back then if I wanted to link C and Pascal code (Pascal being the calling convention, not the language), I had to tell the C compiler that the compiled function I was linking with used the pascal calling convention using something like "extern "PASCAL" func(a,b, c)".
@@cquezel yeah, there's that as well. Many libraries use something similar like
```
#ifdef __cplusplus
extern "C" {
#endif
```
in the header files because there are several things that are legal in C but not C++.
I'm pretty sure those keywords will never go away for the simple reason of backwards compatibility. A language as old as C is going to be dragging around with it certain legacy notions because you simply cannot afford to break working legacy code. In some ways something to be happy about as they don't really cause any real discomfort for people (does anyone really want/need a variable named "register"?) but shows how long we've come in our journey.
That was unexpectedly chaotic and I loved it
The register keyword is not as useless as it may seem. The functionality is used for two things. First of all, the fact that godbolt spits out register movement instead of RAM accesses is because the optimizer puts register there even if you don't. Without register being there in the first place, that wouldn't work. (Sure it can be done and is most likely being done not my stuffing the string in there but the functionality but the point stands) Secondly, it can be used for it's original purpose in low level computing libraries with long and complicated functions. It does make a difference when the function is complex enough that the optimizer may make mistakes, like multiple matrix multiplications for shading or similar. Sure splitting them in blocks makes it easier but the additional method calls and stack access does make a difference when something is run on a million verteces 144 times per second or more. Especially if it is older hardware that cannot profit as much from parallel computation on GPUs or if the task is another that cannot be done in parallel at all.
A common complaint about c, it is not portable from processor to others. It is unfortunately common to use local keywords to pretend portability.
You were right! You have 34 C keywords. You write down “unsigned” twice.
If you want to directly interact with your machine such as reading a specific cpu register directly, "register" is indispensable. so yes it is not useful for those not dealing with low level but for core embedded guys it is quite useful.
How do you read cpu registers in C?
@@matthieusimon7836 For ARM architecture e.g.
register uint32_t __regfpscr __ASM("fpscr");
can be used to read floating point status control register.
'Goto'. I just happy that I didn't grow up when BASIC was the first language you learned...
The register keyword is not useless. Here is a short example:
#include
int main(void) {
register const a = 23;
int *b = &a;
*b=42;
printf("%d
", a);
}
This program does not compile, but if you remove the register keyword it does compile, the output is then 42 on my PC and 23 on Termux on Android.
I think you wanted to write `*b = 42;` and `%d`. But anyway that's an UB.
When we switched from 32 bit to 64 bit i never understood why long just didn't become 64 bits and int stayed at 32 bits
You counted correctly in the voiceover, there are 34. Unsigned is listed twice.
Auto uselessness 11/10
On microcontrollers I always use stdint, simply because I want to use as little FLASH and RAM as possible. On desktop for better or worse I stick with the regular short, int and longs.
extern: Most compilers assume that undefined functions that do have function prototype are already external. However, you cannot do this with global variables. If you declare a global variable in a header file, you need to mark it external explicitly. -- int/long/short: As you said, it's better to use the types declared in "stdint.h" to request specific types, unless you really don't care about the size. "long double" often translates to 80 bit IEEE floating-point as implemented in the FPU sections of Intel/AMD CPUs, but on other CPUs, it could be something different. -- "register" is _sometimes_ useful to trigger optimization when the compiler doesn't select a variable for optimization (which can happen if you have many local variables in the same function). Also, "auto" can be used to force the compiler to NOT select a variable for optimization. Again only relevant in functions that have many local variables. "volatile" on a local variable will similarly suppress optimizations (but it's mainly intended for global variables).
What you originally said at 0:45 was correct. There are 34 keywords, not 35. The list on your slide counts "unsigned" twice.
😂 thanks. I guess I need to slow down a bit when editing these videos.
*For register keyword, you will see changes if you change the optimization level*
of the compiler and whether avx or sse code is generated.
@johnsmith1953x You mean "Controls to manual, captain, I'm taking over"
I once used a specialized C compiler for high performance custom processor that recognized Global registers. The register keyword enabled this behaviour. For extreme performance the global register enabled custom-hardware level capability. I've never used the"auto" keyword in the 200kloc of C code I've written
My take on those:
extern is useful for writing 'extern "C"'. Yes, that's C++ and not C, but oftentimes you need to write C code that will stay valid in C++, and 'extern "C"' is necessary for that.
stdint.h types are generally better than short, int or long, but... stdint.h defines them in terms of typedefs into, well, short, int and long. So these are necessary to implement the standard headers at least ;) There are also a ton of headers that declare short, int and long arguments and return values, and I'd argue that when you declare a variable with the intention of the variable to go in or out of such API, it's the best and most portable approach to just stick to the same declared type. Which will end up having a different size on different platforms, but that's the problem of the library you're calling, not with your code.
register is sometimes useful when used together with compiler-specific extensions, to actually access a specific CPU register in low-level code, without using outright assembly.
auto is completely useless.
Func fact, the one of the only instace where valid c code isnt valid cpp code (at least to my knowlage) is when the "restrict" keyword is used.
@@ItsCOMMANDer_ Not true. Simple example:
char *ptr = malloc(42);
malloc() returns a void pointer. In C, that can be implicitly cast to any other pointer, in C++ that's a compile error.
There are *a lot* more subtleties like that. Empty structs have a different size in C than in C++. Many C features appear in C++ very late, e.g. C99 designated initializers were only made legal C++ 20 years later (although things like that are widely supported as compiler extensions). It's not super hard to write C code that is also legal C++, there are even compiler options like -Wc++-compat in GCC that will alert you about many such subtleties. But they are there, and it's also pretty easy to stumble upon one of those.
@@kFY514 bruh, my cpp compiler (g++) only gives an warning
Isn't extern required to avoid duplicate global variables? In the code base I work on we use extern regularly. I seem to remember leaving that word off one time and it resulted in a linker error complaining about duplicate symbols.
Arguing that people should use the standard integer types instead of "long" or "short" is also ridiculous. Firstly, "long" and "short" are not defined by their size. They're defined by their minimum numeric range, with intentional wiggle room for one's and two's complement integer encoding. The closest thing to a "long" in stdint is int_fast32_t, and even that isn't exact. int_fast32_t, a is guaranteed to be at least a certain size. Long is guaranteed to support at least a certain range, but there's nothing stopping a compiler from using say, a 36bit integer on some weird architecture that has nonstandard word sizes.
I was thinking 'auto' from the start of the video. I've honestly never used it. Now 'register' was in my vocabulary from first using 'C in the mid 80s.
extern is definitely not useless, although you can write function declarations without extern. the only real use is a global variable or global constant.
long is definitely not useless as stdint is a new addition to C and cannot be used on some systems since it was added in C99 (so it will probably not exist in pre 1999 computers). It is a myth to say that this will make your code more "portable".
auto seems to be more of a relic coming from b. maybe in very early inv development versions of C you had to use it? register can also be seen more often in old programs. These two "truly useless" keywords are really just relics from the past and exist for backwards compatibility.
edit: saw another comment about how register is very much so important in microcontrollers, where paying attention to the stacks usage is important.
as for long double, fair enough. but using it wont hurt your code! just because you cannot guarantee that the compiler will use higher precision floats doesn't mean its not worth using for the compilers that will.
this video is honestly just kind of embarrassing. it ignores the rich history of C and just passes off all of these slightly-less-useful-as-time-passed features as "idk why that's there its useless." as well as ignoring everyone who is using C for a different purpose where these keywords are actually used and for good reason. i'm surprised you didnt bring up volatile.
When developing for the TI-84 Plus CE, you can't rely on the size of an int being 32-bits wide. The eZ80 is a weird backwards-compatible version of the Z80 processor, and the eZ80's 2-byte long registers are now 3 bytes long. The size of an int changed to 24 bits because it fits the size of the multibyte registers. There is some work being put in to add 48-byte "short long long" numbers as well. It is strongly recommended to use the *unsigned* integer types as specified in stdint.h because it is a little faster to do unsigned arithmetic on the (e)Z80. There is also a floating-point implementation, but there isn't a double-precision floating point implementation yet. The double type is an alias to a float, but a double precision floating point implementation, if made, would probably get the name long double. However, TI conveniently provides routines for 14-DIGIT precision floating point values in their OS.
The extern keyword is useful if you're doing Python + C programming, with the latter providing the oomph in computation.
Here you got a great example of how you define a word. If I talk about "portability", I talk about the possibility to compile a program across a *WIDE* range of compilers - even legacy compilers like Turbo C or Coherent C (this one only supports K&R, really!!). No fancy *stdint.h* to be found here. The only way to get an idea of what a certain core datatype actually does is to make a program with a lot of *sizeof()* calls - or consult the documentation. So in my world *long* is not quite superfluous.
What I tend to do though is make my own .h file with a lot of *typedef* where I can abstract certain datatypes like 32 bit signed, 16 bit unsigned, signed characters, etc. By matching certain compiler macros with certain definitions (e.g. *__TURBOC__* or *__GCC__* ) I can make the process somewhat transparent. Disadvantage: every time you add a new compiler you've got to update the thing.
I agree with *auto* and *register* though. I've been programming C for about 40 years now and I *never* used them.
My two cents: Register is important when the function has more arguments than the available temporary registers of the CPU you are using and you want to execute some loops/algorithms faster and not let the compiler to use the stack for that small code
Your example for register is useless. You need to pick closer to more real-life examples for it to make effect.
For starters, you need more variables than how many what CPU registers you have, otherwise, the registers will just be used.
If you have code that is not so common to be written and you know how frequently some variables are used, register can help when doing dev builds without optimizations.
I'd give register a honourable mention for that reason. Not needed any more for nowadays compilers but it was crucial to extract every single bit of performance in old machines and to speed up the compiler.
For *very* old compilers only. I tried the dhrystone benchmark with and without register in the late 80s, the compiler's optimizer was better in assigning variables to registers. The register keyword blocked sensible register use, so resulted in slower code.
@@Hauketal I've used on recent compilers (clang and gcc) with code from work and it worked on about half of the tries. I hand picked the places where I thought it would make sense to send and keep a certain variable to and in the register but the compiler didn't decide such. (Do note the examples were not obvious code on how to optimize). And it worked on -O0!
However, after selecting to compile in -O1 or above, the compiler picked up those places too to place in registers and it was not needed any more.
extern is useful in header files for declaring stuff that works both in c and cpp
Referring to how C code is writen in Embedded Software following the Static Architecture of AutoSar (for Automotive) with the rules applied from MISRA and CERT and the rules that are also applied according to safety (ASIL level),
then you would understand the importance of some keywords like: extern and why we just don't use the standard libraries.
My favorite useless keyword in C is "inline". Because these days the compiler decides on it's own if it wants to inline a function call or not, and will generally ignore your wishes on the matter. Ye olde C compilers did need you to tell them which functions should be inlined and which shouldn't, but modern optimizing compilers can figure it out on their own.
Auto and register are complementary. In the elden days, accessing variables on the stack required a lot of address calculation. If your compiler spported auto, you tell the compiler to store a variable in a register, ie. Single instruction access for a highly used variable and auto when the amount of accesses was minimal. Now a days
, native addressing modes include stack plus index and stack plus frame plus index and more. The use of register also alters the size of the cpu context that must be saved for interrupts. By the way, inline is also only a hint and usually not supported. The three keywords listed here assume a compiler with very limited optimization capabilities and are not in any way portable.
"auto" is complementary to "static".
"register" could be assumed as third option.
Auto, register, static and extern are all storage class specifies. Auto is scoped limited to a function. Static sets scope based on its placement. Extern scope is outside of this module. I don't know if register is limited to a function scope. If so, I agree with your comment. Because auto and register exist only for one instance.
Absolutely "auto" is useless because it's inherited from B language and everything is "auto" by default even we don't explicitly define it.
It’s 34, not 35, after all. You have unsigned listed twice.
@10:15 The auto keyword is mostly useless, with exception of the fact that it allows you to omit a type specifier. In C, everything is an int by default, so this is perfectly valid code:
main(argc, argv)
char *argv[];
{
for (auto i = 0; i < argc; i++)
puts(argv[i]);
}
You didn't miss count, you had unsigned listed twice.
I had to use extern in a separate global variables header file (clang 13), otherwise the compiler kept screaming that it couldn't find the variable.
You can't take the address of a register variable. It can be used to say, I don't want to alias this variable...
so microcontrollers do not have a lot of these libraries and even if they did, space is a precious resource in many of them. Using a long is probably the best solution for many of them.
What I've learned from this is that C is VERY complex compared to other languages
I find it very simple. It isn't so bloated and fast to learn every concept of C. I would even say C is the easiest language to "master", because there arent that many features to learn, let alone the standard library is very small compared to other languages. But you can shoot yourself in the foot very easy. Plus it feels like 50% of C behavior is undefined. I mean it starts vague datatype sizes defined as char
Register is mostly useless now. It still has meaning a few places, but they are few and far between. In the far mists of C pre-history, the PDP-11 had 8 integer registers. One was used for the PC, one for the stack, and one for a frame pointer. The other five were for general use by the compiler, but... r0 was used for return values (ints), and r0 and r1 were used for return values (long). That left r2, r3, and r4 available for 'register variables', and the C compiler allocated them in r4, r3, r2 order.
Generally, they were a hint that these variables were used a lot and, the PDP-11 having no cache to speak of, would result in smaller and faster code if the named variables were put in registers, rather than fetching them off the stack for each use. In these days, however, the poorest common compilers do a much better job of type checking, optimizing, and scheduling code than even the fanciest lint tools of yore. And so for 99+% of code, register is meaningless.
(I will point out that the example used in this clip isn't a good one: even the compilers of yore would optimize a 'register' variable associated with r2-r4 to r0 if it was returned as soon as it was computed. So I wouldn't expect that example to have made a difference in compiler output even back in the days of the dinosaurs if optimization was enabled.)
Long after the PDP-11, although partly as fallout from the PDP-11, the register keyword found significant use in the x86. In PDP-11 days, all floating point arithmetic was done in 'double' precision, and results stored to variables in whatever type the variable held. It was therefore helpful (numerically) if critical values, which might be 'float', were nonetheless held in registers, where they had type 'double', and used accordingly. This avoided loss of precision, and for things like dot products, could yield significantly better numerical results.
The x86 had a similar issue: in the old days (pre-SSE2) the x86 FP "stack" model holds/held internal values with 80 bits of precision, but storing a value to a memory location truncates/truncated it to 64 (double) or 32 (float) bits (according to its type). So putting critical variables into a register for the duration of a running sum or sum of products can/could significantly improve both speed and numerical accuracy.