C Primer
enlightenment.org
enlightenment.org
[EDIT: Reading a little more I'm revising down as summary. There's quite a bit that's amazingly tortuously explained, or slightly wrong. e.g. '//' doesn't have to be the first 2 chars of a line, it's anywhere to EOL. He refers to both parentheses and braces as braces! Explanation of needing ';' at EOL is not helping any beginner who;d be better off with the traditional "all C statements end with a ';'".]
Couldn't call it a primer though as it takes an express trip through the features from my first program to pointers to functions in a dozen page downs.
I don't think "downside of function pointers is the added mental load to handle the indirection" rates as enough of a warning, at all. How's about letting our beginners trip up on arrays and pointer arithmetic, in a few example programs, before giving keys to all the self inflicted weaponary?
"void An undefined type, used often to mean no type (no return or no parameters) or if used with pointers, a pointer to “anything”
Undefined? That strikes me as a way for the beginner to get totally the wrong idea.
It's used to indicate no return value or parameters in function definitions. A void pointer is generic - it can be cast implicitly to anything. You can't do pointer operations until it has been cast to a type. It is not undefined.
Almost all C statements and declarations end with a semicolon. But compound statements don't.
"Other lines that are not starting a function, ending it or defining control end every statement in C with a ; character"
No, arrays are not pointers.
"A String is generally a pointer to a series of bytes (chars) which ends in a byte value of 0 to indicate the end of the string."
No, a string is by definition "a contiguous sequence of characters terminated by and including the first null character". It is not in any sense a pointer.
Anyone who things arrays or strings are "really" just pointers should not be writing C tutorials.
(See section 6 of the comp.lang.c FAQ, http://www.c-faq.com/ .)
The language is partly at fault for a couple of rules that implicitly convert or "adjust" arrays to pointers in certain contexts, but the solution is to explain the differences, not to gloss over them.
On the contrary - even in small details, like for example the equivalence of p[i] and * (p+i), so you can legally write 3[p] in C...(==p[3])
What creates the confusion in C is that arrays try very hard to decay into pointers at any occasion and on top of that you have "fake" arrays when they're declared as function parameters (something that's probably the most insane "feature" of C IMO. And don't get me started to the a[static n] syntax that solves nothing and introduces even more confusion on top of adding yet an other completely new meaning to the 'static' keyword). Most crucially they behave very differently when it comes to using sizeof.
It's also very important to understand the difference to be able to understand `const char p = "abcd"` vs `char p[] = "abcd"` or `struct s { / ... /; char data; }` vs. `struct s { /* ... */; char data[]; }`.
p[i] is equivalent to (p+i), and that's possible because both the indexing "[]" operator and pointer addition "+" operator require a pointer and an integer as operands. (The pointer needs to point to an element of an array object, but that's not enforced at compile time.) For p[i], it's very common for the pointer operand to be an array expression that "decays" to a pointer.
(3[p] is valid because pointer addition, like integer and floating-point addition, is commutative. It could* have been defined to require a pointer as the left operand and an integer as the right operand. The decision was made when the distinction between integers and pointers wasn't as strong as it is in modern C.)
I also think the section about "the machine" is interesting but it's a bad idea in a C tutorial. When you code C you code for the C abstract machine, not your CPU. It's very important to understand that difference, especially if you want to write portable code. For instance integer overflow is most likely very well defined on your machine, but it's not in the C virtual machine. C is not a macroassembler. Furthermore I really don't think the details in this sections are relevant in the early stages of learning C.
There also are a few technical inaccuracies:
>So bytes (chars) can go from -128 to 127, shorts from -32768 to 32767, and so on. By default all of the types are signed (except pointers) UNLESS you put an “unsigned” in front of them.
That's true on most systems but "char" can be either signed or unsigned (IIRC on ARM they default to unsigned) and the C standard only gives minimum ranges, so 32bit chars and 64bit shorts are theoretically possible (and can occur on systems where the smallest addressable value is larger than 8 bit such as some DSPs). It's pure nitpick of course but why even bother mentioning that in a C intro? Introduce stdint.h instead, that's actually very useful for any C coder.
>You can have a pointer to pointers, so de-reference a pointer to pointers to get the place in memory where the actual data is then de-reference that again to get the data itself
>So keep this in mind as a general rule - your data must be aligned.
>Note that in addition to memory, CPUs will have “temporary local registers” that are directly inside the CPU.
Are we seriously talking about nested pointers, alignment and CPU registers before we've even introduced printf? If a newbie manages to get past that and continue through the examples I congratulate them.
I don't want to sound too harsh, it's definitely very commendable to try and share your knowledge with other people and writing tutorials is very time consuming and not always very rewarding. That being said I definitely wouldn't advise anybody to start learning C using that document, especially since there's already a wealth of resources for learning C, both online and off.
Indeed. I worked at Cambridge Silicon Radio. Their Bluetooth chips often had two processors: a XAP for the BT stack and a Kalimba DSP for audio processing. They had 16-bit and 24-bit chars respectively. About 1 billion such chips have been sold, so it's not just some wacky architecture invented by a mad bloke in a shed.
No, printf is introduced in the very first hello world example. But the concept of format strings is not explained, nor are variadic functions, and no documentation for printf is linked.
Also, the example
#define MY_TITLE "Hello world"
printf("This is: %n", MY_TITLE);
is really quite broken. %n needs and int* argument and writes the number of characters written so far to it. A string constant is not only of the wrong type, even more importantly it is not writable (though not const).Of course a modern compiler with -Wall would catch this, but the tutorial never mentions any flags you might want to pass to your compiler...
Oh, in the Dvorak layout, "n" and "s" are next to each other. Maybe that's how.
I code for my cpu, when I code C.
Here's a quick example to demonstrate why I mean, consider the following code that increments a variable and returns it. If the incrementation overflows it returns 0 instead:
int increment_or_0(int a) {
int res = a + 1;
if (res < a) {
/* Overflow */
return 0;
}
return res;
}
If you code "for your CPU" and your CPU is, say, x86-64 then you might think that this code ought to work. You add one to a and if the result is smaller than the original value of a then an overflow occurred. With 2's complement arithmetic that makes perfect sense. Yet built with gcc 7.3.0 using -O3 the compiler generates the following assembly: lea 0x1(%rdi),%eax
retq
In other words you have the incrementation but the overflow check is nowhere to be found. That's a perfectly valid optimization for a C compiler to make, because your code relies on an undefined behavior.In this case if you really want to write your test that way you can use the "-fwrapv" to tell GCC to assume fully defined integer overflow but then you're coding in a nonstandard dialect of C.
(For developers: additionally, as a non-standard feature, most popular open source compilers (i.e., gcc and clang) support an option called "-fwrapv", which does not treat 2's complement overflow as Undefined Behavior. This is useful for getting a large legacy codebase working on newer compilers, if it makes assumptions about 2's complement. Until the new C standard is published it's probably best to avoid relying on 2's complement overflow in new code.)
[0]: https://twitter.com/jfbastien/status/989242576598327296
This is explicitly mentioned in the page linked in that tweet:
> Status-quo If a signed operation would naturally produce a value that is not within the range of the result type, the behavior is undefined. The author had hoped to make this well-defined as wrapping (the operations produce the same value bits as for the corresponding unsigned type), but WG21 had strong resistance against this.
That's why I think it's a bad idea to give all these nitty-gritty details about CPU registers and the way the heap and stack are laid out in RAM because in practice C doesn't say anything about that (well, there's the register keyword I suppose). For a C tutorial it's more important to teach the C abstract machine and its rules than the way they map to a given piece of hardware.
Take for instance this sentence from the article:
>You can even tell the compiler to make sure it has an initial value. If you don't, its value may be random garbage that was there before in memory.
I don't think that's a good thing to say. What you want to say is that the value is undefined and reading it is forbidden because that's what it really is. If you decide to use an undefined value somewhere in your code the compiler might swoop in and optimize it away, not use "random garbage that was there before in memory". Undefined is not random, undefined is not garbage, undefined is undefined. You might print it twice and get two different results. You might have a condition "if (uninitialized > 0)" and a condition "if (uninitialized <= 0)" and both may run, or maybe none will. Because there's no such thing as "random garbage in memory" in the C abstract machine, only defined and undefined values which are semantically different.
I think most of the things you point would be valid for a generic C tutorial intending to teach C, but less so for a tutorial intending to teach the bits of C the author thinks is most important to be able to understand and/or contribute to EFL.
If the coder already knows the basics of C and you want to bring them up to speed on the details they'll have to know to work on your library then I don't think you'll need to explain that "A function is a basic unit of execution" or that "Memory to a machine is just a big “spreadsheet” of numbers" for instance. This is clearly meant for complete novices, but at the same time it introduces too many concepts at once to work as a tutorial to learn C from scratch.
Instead focus on how your libraries are architectured, how the pieces fit together, the rationale for why things are this way etc... See for instance "linux device drivers"[1] as an example of what I'm talking about. I know C and this "primer" doesn't give me anything valuable if I actually wanted to hack on their libraries.
On the other hand if the coder really doesn't know what a function or a struct is then it's probably a better idea to tell them to follow some actual C tutorial and come back when they're done because this particular text will probably overwhelm and confuse them, not actually give them the skills necessary to actually contribute decent C code anyway.
All threads can read and write to all memory within that process. This is extremely dangerous, so avoid threading until you have fairly well mastered C without threads.
If you must use threads, even if you are experienced, sticking to a model where threads share as little data as possible and very carefully hand off data from one thread to another and then never touch it again is far safer.
Much like the enlightenment codebase itself, unfortunately. If you want to see some quite bad C code, just check out the source.[0]
[1] https://cs50.tv
int main(int ac, char **av)
and then try to figure what the following mean (what type they are, and whether each of them is a pointer or a pointed-to thing, or both: av
*av
**av
av[0]
etc.I was once teaching a class on C to a group of experienced programmers (10+ years each, but new to C), and after I explained the char
**av
stuff, one of them said "Now it is overhead transmission" :)One simple example showing value semantics would’ve made it click for me, and things only get more interesting when you talk about performance.
Here is a visualization I created to illustrate linked list: https://goo.gl/12juwA
Look at 68000 assembly language. In particular indexed addressing modes.
In C really that's almost all they are.
That said, I wish there was a 21st century update to the K&R book, to take into account improvements made the at least the C99 standard, maybe even C11, update the programming style a bit to avoid some of the more egregious risky behaviors (always use strncpy), and some coverage of the preprocessor. All of which while still maintaining the clarity of the original book.
I disagree; you should almost never use strncpy. Its name implies that it's a "safer" version of strcpy, but that's not what it is at all. If the target array isn't big enough, it can easily end up not containing a null-terminated string.
As it happens, I wrote about this a few years ago. http://the-flat-trantor-society.blogspot.com/2012/03/no-strn...
I wrote C/C++ for years in a scientific setting and didn't completely conquer the damn things until I had to write some assembly for a hobby project.
The actual internals not so much, to the point not everyone is pleased with its use on Tizen.
specially in a easy to understand the basics kind of article.
You understand what linking is when you understand how addresses in machine code work, you understand the call stack when you understand the principal layout of variables in memory, etc.
"It won't cover esoteric details of “strange architectures”. It pretty much covers C as a high level assembly language that is portable across a range of modern architectures."
BTW I understood the power stack when I tempted to code a language...
> struct sandwich
> enum filling