Modern C, Second Edition
gustedt.wordpress.com
gustedt.wordpress.com
I know that, unlike "Modern C", "21st Century C" can't possibly cover C17 because that book was released in 2014.
But otherwise, what would be notable differences? In terms of style, correctness, idiom, depth, and breadth?
"21st Century C" is for people who have learned C in the past but need to brush up their knowledge with modern practices.
As someone who learned C at the time of the first edition of the K&R, "21st Century C" would be more useful if I ever had to code in C again (which hopefully I won't). A refresh, 5 years later, would be useful, though.
https://gforge.inria.fr/tracker/?group_id=6881
But the 'Bugs' link is literally on the first page of the book.
And the ergonomics of modern C++ look absolutely horrid compared to actual modern languages.
Do you have a link? Your comment is the highest Google result for that quote.
> And the ergonomics of modern C++ look absolutely horrid compared to actual modern languages.
True, but if you want to write "C as it is, with some niceties", then you're not talking about modern languages.
I found it by hyping up Jai's "using" keyword. Then someone told me that Odin has the same thing: https://odin-lang.org/docs/overview/#using-statement
They have lots of book publishing resources: https://www.ctan.org/topic/book-pub
[1] https://tex.stackexchange.com/questions/1319/showcase-of-bea...
Is it? I skimmed over it, and I don't find the typesetting / listings / tables / highlighting particularly appealing.
https://www.waterstones.com/book/modern-c/jens-gustedt//9781...
Also book depository which I think delivers worldwide:
https://www.bookdepository.com/Modern-C-Jens-Gustedt/9781617...
mingw: For native build and execution. A lot of Windows IDE's will just work with mingw.
msys2: For posix compatible build but native win32 targets. Useful when your project uses autotools. Like when trying to build someones library. msys2 just tends to work. Trying to get autotools to work under windows tends to exceed my patience, time and brain cells.
cigwin: for a posix compatible build and runtime environment.
Also gcc can be built as a cross compiler. The two times I tried it is was more straight forward that I would have thought.
you can build also with gcc for cygwin, but you introduce a cygwin.dll (not sure about the name) dependency.
For me my main use of msys2 is as a shell where i run scripts and command-line utilities with the build stuff being a distant secondary use.
Avoid.
> implements strcpy as `while(dst++ = dst++);
The book is full of stuff like that.
"[...] none of the popular Open Source distribution such as BSD or Linux has chosen to make either available to their users. At least one (GNU C Library) has repeatedly rejected proposals for inclusion [...]"
"[...] As a result of the numerous deviations from the specification the Microsoft implementation cannot be considered conforming or portable."
It's in C11. Perhaps Red Hat use non-compliant compilers, or very, very old ones?
Why would that be?
The main thing to watch out about it these days is that K&R is a bit carefree about the numerous ways where you can shoot yourself in the foot using C. For example, K&R famously implements strcpy as `while(dst++ = dst++);`. These days, most people would say that code is excessively terse, and that you shouldn't be using strcpy in the first place (because it can potentially overflow the dst buffer).
while ((dst++) = (src++))
I believe.
Also terse it may be, but this (assuming the missing asterisks are restored) is a good, idiomatic implementation of strcpy. Whether strcpy is good or evil is a separate discussion.
Zed Shaw is sometimes a bit controversial but he wrote a "learn c the hard way" (or something similar) that aims to obsolete K&R.
Explanation of jargon seems to take priority over explanation of the language - the term "string literal" is explained before the concept of a function.
C Primer Plus (6th Edition) (Developer's Library) https://www.amazon.com/dp/0321928423/ref=cm_sw_r_cp_api_i_bg...
21st Century C by Ben Kelemens is also a good book.
I would recommend that as well. Dr. King was one of my favorite professors, but I bought that particular book on my own.
Just from a quick glance, Modern C appears to be a much more in depth book. It appears to cover things like memory alignment, advanced types like unions, malloc, threads, etc.
Learn C the Hard Way is more of a quick introduction. You learn the basics, like pointers, basic data structures, basic types and then learn to do something interesting with those basics (build a web server). It also teaches some valuable real life practices that Modern C doesn't touch on, like how to structure your project, makefiles, how to use Valgrind and gdb.
Modern C has an entire chapter called "malloc and friends" where it goes into much more depth about malloc, calloc, realloc, etc.
The book is a good introduction to programming in C and I'm glad I read it. You obviously have a history with the author and I'll thank you to keep your past impressions out of this particular discussion.
Every other C book seems to disagree, but OK.
Does the book cover the -> operator? Does it mention free and realloc? You need those to do stuff, at least.
> You obviously have a history with the author
How is this obvious, since it is incorrect?
That said, yes it is damned annoying to have someone that doesn't understand the intricate details of the language poop all over that community and act as if "well i've contributed lots of code in C, why listen to a bunch of language lawyers, that isn't important anyway" as if the two are mutually exclusive. There are some of us that have been using C for years for actual work and are also intricately familiar with the prickly details of the standard. Those are the people that you should be looking for C books. Zed Shaw consistently shows he would rather hear the sound of his own voice and diminish things that are uninteresting to him rather than put in that work to become an expert on all facets of the topic he is claiming expertise.
https://what.thedailywtf.com/topic/16422/zed-shaw-gets-schoo...
[1] https://www.amazon.com/Programming-Language-2nd-Brian-Kernig...
If there is a book as good as K&R and covers the new ideas, I'm interested to learn about it! :-)
Head First C by Griffiths and Griffiths
and
Expert C Programming: Deep C Secrets by van der Linden
You do realize he's doing that for illustrative purposes, right?
By the very fact that this is a book, it presumes that the intended audience consists of visual learners. When working with visual learners, if something is erroneous, it is imperative that it be explicitly and visually called out as such. In linguistics pedagogy, for instance, this is done by marking any ungrammatical construct or example not found in natural language with an asterisk every time.
https://www.ling.upenn.edu/~kroch/courses/lx101/assign96.htm... https://linguistics.stackexchange.com/questions/338/meaning-... https://www.departments.bucknell.edu/linguistics/lectures/as...
This book does not do this. It freely uses deliberately bad code in example programs. This can only result in confusion on the part of learners. It is therefore inconsistent with pedagogical best practices. I would not recommend it.
pg 59
This was originally a comment about a book I thought was the same but merely had a similar title.
The const qualification is in the type; it is not a direct attribute of foo, so putting the const closer to foo means nothing.
But yes, it is a matter of opinion.
foo: int const;
And similarly, function declarations might also take a more ML-like syntax. C seems more like it wants to be an adjectives-before-nouns type of language.Which would explain why it seems so odd to me; I spent a fair amount of time in C-style languages, but typically only ones that lack pointers.
const int *foo;
when they meant int *const foo;
or const int *const foo; int *const foo;
It's mostly useless to declare a variable that cannot be assigned. But very often people write const int **foo;
When they intended const int * const *foo;So all functional languages (where immutability is the default) are mostly useless?
What is or is not good practice in C has little bearing on or relationship to functional languages, so I'm not sure why you bring them up in this context.
int *const current_foo = &items[i].bar.foo;
before some complicated manipulations on current_foo.(A decent (SSA-)optimizing compiler will produce identical code with and without the declaration.)
Let's see how many times this occurs in my TXR project (~75K lines):
$ git grep -F '*const'
$ git grep -F '* const'
linenoise/linenoise.c: const wchar_t * const *lelem = convert(const wchar_t * const *, larg);
linenoise/linenoise.c: const wchar_t * const *relem = convert(const wchar_t * const *, rarg);
One place: a comparison function for a qsort call, where a vector of const wchar_t * strings is being sorted. I didn't write this (though I did convert it to wchar). qsort passes const void (star) pointers to the elements, which are const wchar_t (star), thus things end up like this.How about Linux kernel 4.9? There are numerous occurrences of this involving arrays declared at file scope, like this:
static const char *const foo[] = { "abc", "def" };
Let's filter those out to find other uses: # lines that start with at least one tab and contain *const,
# possibly with optional spaces between * and const:
kernel-4.9$ git grep '^[\t].*\*[ ]*const\>'
kernel-4.9$
Wow, not a single result!Let's relax that and allow a space or tab [\t ]. Then there are lots of false positives due to items in block commented lines starting with a ' asterisk' sequence. If we require at least one lower-case alpha character before star-const, we filter most of these out:
kernel-4.9$ git grep '^[\t ].*[a-z].*\*[ ]*const\>'
Documentation only, showing useless const parameters in a function prototype decl: Documentation/cpuidle/driver.txt: const struct cpumask *const coupled_cpus);
Documentation/media/kapi/v4l2-controls.rst: s32 skip_mask, s32 def, const char * const *qmenu);
False positive, in a table inside a block comment: arch/powerpc/xmon/ansidecl.h: PTRCONST `void *const' `char *'
Useless const parameter in function pointer signature. The definition of these operations are not required to use const: drivers/net/wan/lmc/lmc_var.h: void (* set_circuit_type)(lmc_softc_t * const, int);
drivers/net/wan/lmc/lmc_var.h: void (* watchdog)(lmc_softc_t * const);
Real result: drivers/of/fdt.c: const char *const *compat)
Real result: drivers/staging/vc04_services/interface/vchi/vchi.h: const char * const vll_filename; /* VLL to load to start this service. This is an empty string if VLL is "static" */
Fluff sibling: drivers/staging/vc04_services/interface/vchi/vchi.h: void * const bulk_handle );
Low-value const parameter in function definition: drivers/usb/mon/mon_text.c: char __user * const buf, const size_t nbytes)
Real result (Yacc/Bison skeleton origin): scripts/genksyms/parse.tab.c_shipped: YYSTYPE const * const yyvaluep;
scripts/genksyms/parse.tab.c_shipped: YYSTYPE const * const yyvaluep;
scripts/kconfig/zconf.tab.c_shipped: YYSTYPE const * const yyvaluep;
scripts/kconfig/zconf.tab.c_shipped: YYSTYPE const * const yyvaluep;
Useless const pointer arg in prototype: tools/lib/subcmd/parse-options.h: const char * const usagestr[], int flags);
That's it; a mere spec of dust relative to the LOC count in this tree, half of it useless.Changing it to
$ git grep -P '^[\t ].*[a-z].*\*[ ]*const\W' v4.9
returns 1,779 matches. Here are some from kernel/ v4.9:kernel/time/posix-cpu-timers.c: struct signal_struct *const sig = tsk->signal;
v4.9:kernel/tracepoint.c: struct tracepoint * const *end)
v4.9:kernel/tracepoint.c: struct tracepoint * const *iter;
v4.9:kernel/tracepoint.c: struct tracepoint * const *end,
v4.9:kernel/tracepoint.c: struct tracepoint * const *iter;
Many of the matches are the same as your "array of string constants" example, but just defined at local scope. And many are just constant local variables which some may not consider worth making const. The ones from tracepoint.c above are probably walking an array of const pointers by pointer instead of by index for example though, which is a common case where pointers to const pointers are used, which are legitimately useful.The fiddlyness of const type declarations is definitely one of my least favorite parts of const semantics in C and C++.
"const int * foo" means that foo is a pointer to an int that is constant.
Which could also be "int const * foo" foo is a pointer to a constant int.
But since the const qualifier for pointers can't be reordered like this, I think the point is that it's a better practice to have the const come after, so that it ALWAYS come after in your codebase regardless of the context?
C++ rvalue references are a great example. Whenever I see them I have to go back and relearn the concept because accounting for the language feature sometimes feels more complicated to get right than the unsafe pointer chucking it replaced.
Every major system API I can remember working with that didn't hide const behind a typedef (like Win32's LPCSTR and whatnot) has it const first.
Speaking of Microsoft, though the API's hide const with a typedef, the MSDN samples are predominantly "const char" order.
I've never seen a man page with "int const".
Searching all of the man pages I have installed on an Ubuntu 18 system here (literally grepping through all of /usr/share/man), I found just one with "char const" or "int const". Namely this one:
$ man guestfs-hacking
This contains: int
commandvf (char **stdoutput, char **stderror, unsigned flags,
char const *const *argv)
In the same man page, there are other declarations that have the predominant convention: int
commandrf (char **stdoutput, char **stderror, unsigned flags,
const char *name, ...)
You just worked on some unusual code bases, and are glossing over the language and system API's those code bases depended on, and their respective docs.Further, C is not like English and even if it were, one could not conclude that it should be or should continue to be. Moreover, that it was designed by English speakers is not evidence that it is like English or should be like English: most assembly languages were also desiged by English speakers.
Though these can be reordered, it's clear they were intended to follow English.
In a compiler implementation, it's troublesome to enforce the order compared to processing a simple list and setting/checking flags in some data structure, diagnosing invalid duplicates or mutually exclusive situations.
I was speaking of declarators, not declaration specifiers. In C, a pointer type is part of the declarator and not the specifiers. Therefore, the CV qualifier is also part of the declarator except for the one exception of the specifier. Regardless of whether specifiers were intended to follow English (and this claim is dubious), declarators certainly were not, since it's often more natural to read them right to left, and it's generally most natural to read the left most type-specifier last.
example: char *x[10]; Translated to english this is naturally stated as an array of 10 pointers to char. Not char something something array.
Edit: later on I see you've moved the goal posts about the "uselessness" of cv qualifiers on pointers. I don't know what to tell you, I think that's bullshit, and there are a number of viable use-cases for them regardless of whether you have encountered them. I'm just addressing the consistency part here.
There is no choice about where to put const inside a declarator; the position affects the nesting level which alters the semantics. So there is no debate to be had there.
Note that in declarators, the const is necessarily to the left of the thing it qualifies, so why would we put it to the right of int:
int ** const ptr;
^^^ this is what is const
int * const *ptr;
^^^^ this is what is const
const int **ptr;
^^^ ^^^^^ these two are const
In the canonical specifier order, nothing to the left of const is tainted by const: extern const int **ptr;
^^^ ^^^^^^ const and const
^^^^^^ unrelated to const
For the best possible consistency, imagine const to be pissing, while the wind is blowing left to right.Your third example could just as easily and correctly be:
int const **ptr;
^^^^^ this is what is const
True, it is a special case when there is more than one item in the declarator list as to how const applied, but that by itself is secondary to where const goes.Const qualifies the object. Whether the const is applying to the "int" in some way or is an independent type specifier that applies to each declarator is pretty philosophical. I say it's the latter. The fact is, the very allowance of a cv qualifier in the specifier list introduces an inconsistency or imbalance.
Ultimately this is one of the ultimate bikeshedding argument. There's no right answer, and appeals to spoken language aren't particularly compelling when bikeshedding C of all languages. This isn't AppleScript.
I'm not even terribly interested in this particular point. I'm more interested in quashing the notions that bikeshedded opinions of language syntax have some divine providence. It's just bullshit.
Also, declarations can have multiple declarators.
int const *p, volatile **q[3]; // syntax error! not how it works
This isn't bike-shedding. The shed has already been painted. There is a right answer which is not to make your code look weird just because the standard allows it.No "int const", no i[array] instead of array[i], no deceptive trompe d'oeil nonsense like:
int* p, q;
You will not solve any issue in C programming by swapping around declarators.When we write code, stuff that looks weird should signal a bug. When bug-free code looks weird, that is a distracting false positive that grabs my attention for no reason. Don't add gratuitous weird.
Eh? int is a noun, surely? How does int describe an int?
I can imagine cost and long being adjectives for the noun int.
> Takeaway 1.4.5.2 Don’t use the , operator.
Gives me a 404?
Typo in first paragraph of https://gustedt.wordpress.com/category/c/ (mayor instead of major)
> printf("element␣%zu␣is␣%g,␣\tits␣square␣is␣%g\n",
Relative to other books in the series, or do you object to the "historical fashion" style book covers that publisher tends to use?
[1] https://en.wikipedia.org/wiki/Structure_and_Interpretation_o...
I'd recommend uBlock Origin.
There are privacy concerns with this, of course, but DuckDuckGo and modern Safari (with cross-site tracking prevention) helps mitigate a bit of it.
They'll never even notice, and in the meantime you're at risk of all sorts of trackers, malware etc. Plus...you're seeing adverts all the time. I used chrome on android the other day and I couldn't believe how many there were, or how annoying. If you're not clicking on them it means nothing anyway, as far as I can tell. I remember back in the day sites saying "click on the ads - it helps us" but I don't think that's a thing any more. Perhaps I should knock up a script to visit random sites with ads and maybe click on a few, in a container/vm. Would that help?
Don't worry, there's enough publisher-side fraud already doing that for you!
What's happening today couldn't be more dramatically different. Tracking your activities around the entire Internet, then using your own computing resources to auction them off to the highest bidder for every new page you view.
I don't have a solution to the ethical conundrum. But accepting this kind of thing cannot be it.
Once I started using "request policy", then eventually uMatrix, I found I no longer needed to use an ad blocker.
Especially if one browses with javascript disabled.
I occasionally do occasionally see ads, but now they tend to be non intrusive, and for sites I frequently read, can be avoided w/o the use of a blocker (and the regex engines they tend to depend upon).
"//" comments, function prototypes, const, atomic, inline, complex, mid-block definitions are.
Runtime variable local arrays, designated initializers, restrict not. C++ cribbed designated initializers from C, coming in '20. Restrict was cribbed, late, from Fortran.
AFAIU C++ will require that you initialize the members in the same order as defined. It also won't permit you to exclude any definitions. Why even bother?
Somewhat controversially, C permits you to define members multiple times, with the last definition taking precedence. Compilers sometimes warn about this, but I've personally found the behavior useful--I'll write an API that provides a macro with default values yet which allows the user to override any particular definition. I don't think I've ever had a bug in an initialization, at least not involving multiple definitions. That seems like a very easy and superficially useful diagnostic to write, but which prevents useful behaviors; behaviors that named initializer's were deliberately designed to provide.
There is probably no reason to enforce order unless unmentioned elements have a non-trivial destructor, so that could be relaxed in a future Standard. Members that need destruction would need to be destroyed in the opposite order; enforcing order allows reusing code already generated for the containing-object destructor, but in many interesting cases (e.g. C structs) there are no destructors to run anyway. In the others, there is no reason why it would need to re-use the class destructor.
For posterity (I like to leave breadcrumbs for myself):
https://github.com/cplusplus/draft/blob/dc2bff0/source/decla...
https://github.com/cplusplus/draft/blob/dc2bff0/source/decla...
https://github.com/cplusplus/draft/blob/dc2bff0/source/compa...
Edit: there are many languages that have both compilers and interpreters. There are several C interpreters as well. The classification of languages as “interpreted” or “compiled” does not appear to be a sound concept, IMHO.
"Compiled" traditionally means "not interpreted", or rather "compiled to machine code".
oh, and btw: there are multiple c interpreters
> because languages are specification, "books". they are not interpreted nor compiled
Languages are a specification, and an implementation working together in perfect harmony, with absolutely no undefined behaviour at all, yes, keep walking now.
At least... the good ones try to be, cough ignoring small half baked interpreters and compilers I've had the pleasure of working with cough and never touching again.
> The most common implementations of the C language are compilers, yes, but "compiled" it's not a language property
It absolutely is a large part of the C language and worth teaching, and I'd only split hairs in a programming language theory class, this is a book that presumably leaves the reader with a better grasp of C than before. And arguing against it is an exercise in personal experience I assume (you have C interpreter experience I wager?).
Most people are still taught C in terms of a compiler like gcc, or clang. Source code in, object code (for a specific language specification, target architecture, etc) out. Think operating systems, kernel modules, executables, dll's, and, etc.
I never touched a C interpreter (but I am curious at such a beast), but I know that C is fine to be referenced as "compiled".
if languages are "compiled" why does both C and C++ (and also java etc...) need a "memory model"? bare physical address and real threads should be enough, no?
> Languages are a specification, and an implementation working together in perfect harmony, with absolutely no undefined behaviour at all, yes, keep walking now.
never talked about "quality" or "comprehensiveness" of the specs. Just that languages are specs, not implementations.
Yes, in the real world you will use gcc, and you will learn that you write some text stuff and it will transform to an executable for you.
Still, why don't you use the right terminology?
The problem is that, if a book slip on this -- so basic -- definition, how good can it be on the complex parts?
I just don't see this complaint as valid. From the discussion garnered here, C be both a compiled and interpreted language. This is a book about C in the context of the compiled variant, and has references to gcc and clang. The level of detail and nuance when using terminology here is used to match the context the terminology appears in. Excess verbiage for terminology isn't a panacea to confusion, in fact it can increase it. Can't "C is a compiled language" and "C is a interpreted language" both be true independently?
Don't get me wrong, I like reading perspectives like yours, because I just discovered a bunch about C interpreters.
But I feel like you are asking for a "spec" level document on modern C, when you are reading a more practical work on it.
It just so happens that the specification in this case makes very specific demands on the translation environment, closely describing compilation step by step from source files into a program image.
Your "interpreted C" is only C in the loose sense that one may describe other non-compliant but roughly similar implementations.
It does say it explicitly, just using other terminology than "compile" (opting instead for "translate"). See e.g. 5.1.1 in C99.