I can write C, but I'm not confident I'm doing it the right way. There are now decades of forums and blogs I can search through, but I'd love to have a single, clean, clear source of truth for recommendations on how to write C code.
I've started reading "Modern C" by Jens Gustedt. I'm not sure if this is the closest to a modern source of truth.
Note, I'd like my C code to work on Windows, which possibly restricts the "right way" to features provided by C89. But again, I'm not sure, the internet is super clear on this.
Thanks for the tips!
I think Gustedt's book is superb if you're just trying to learn the language, and it does a good job of presenting the most up-to-date standard. I admire the K&R as much as anyone else, but it's not exactly an up-to-date language resource at this point, and it doesn't really provide any guidance on the design and structure of systems in C (that's not it's purpose).
You might have a look at Hanson's C Interfaces and Implementations: Techniques for Creating Reusable Software. That's focused on large projects and APIs, but it will give you a good sense of the "cognitive style" of C.
21st Century C is very opinionated, and it spends a great deal of time talking about tooling, but overall, it's a pretty good orientation to the C ecosystem and more modern idioms and libraries.
I might also put in a plug for Reese's Understanding and Using C Pointers. That sounds a bit narrow, but it's really a book about how you handle -- and think about -- memory in C, and it can be very eye-opening (even for people with a lot of experience with the language).
C forces you to think about things that you seldom have to consider with Javascript, Python, or Go. And yes: it's an "unsafe" language that can set your hair on fire. But there are advantages to it as well. It's screaming-fast, the library ecosystem is absolutely gigantic, there are decades of others' wisdom and experience upon which to draw, it shows no signs of going away any time soon, and you'll have very little trouble keeping up with changes in the language.
It's also a language that you can actually hold in your head at one time, because there's very little sugar going on. It's certainly possible to write extremely obfuscated code, but in practice, I find that I'm only rarely baffled reading someone else's C code. If I am, it's usually because I don't understand the problem domain, not the language.
1. The lack of abstractiom in C? 2. All of the pitfalls and gotchas: Undefined Behaviour, use after free, etc?
I've been writing Rust for a couple of years now, so I'm quite comfortable with low-level ideas, memory management, etc. But C still scares me because it seems so easy to make mistakes, and I also often find it hard to see the forest for the trees.
Perhaps C's just not for me?
I don’t mean for this to be flame bait, but I do think it is worth pointing out and discussing if you desire. The concept of memory management that is presented in a majority of the Rust that I have seen written outside of deep library code and the parts of the low-level implementations of some standard library functions has very little relation to the analogous concepts in C. I’d say that what Rust considers memory management in average code is more similar to programming in C without using any heap allocation. I could be wrong or could just have not seen the right code bases, but there is very little C style byte level ‘data structure’ layout design and then the allocation of those sorts of data using multiple allocation strategies.
I certainly understand that the above mentioned constructs and patterns are really not part of Rust’s idiomatic usage, and that a chunk of C’s allocation options are semi-prohibited because of Rust’s memory model. But, if you are coming from Rust and going into C, the differences are far greater than the similarities in this area.
I’m certainly not questioning your credentials, experience, or ability, I really just feel like this area is lacking any substantial discussion where said discussion is not focused on the merits/drawbacks of the language design. You clearly know Rust and seem to be open to learning or working with C, so it isn’t about Rust v. C, it’s about compare and contrast, and highlighting the fundamental differences that could cause some mental roadblocks.
P.S. Sorry for rambling, Day Light Savings time is kicking my ass.
I'm not sure what you mean by "lack of abstraction in C."
"Abstraction" is not a straightforward property of a language; it's a thing you create with programming languages. It's true that some languages make it easy to express certain kinds of abstractions, but C has it's own way of abstracting things, and it's certainly not absolutely obvious that its way of doing so is inferior to others.
You can compensate for C's lack of compile time protections with run-time tools like valgrind [1] and the various sanitizers in clang [2, 3].
2: https://clang.llvm.org/docs/AddressSanitizer.html
3: https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html
Otherwise you're left alone with yourself and your tooling. Things like valgrind help a lot. But I'll admit that I too find large C projects alarming; it may be better to write "C with classes" code in a C++ environment, so at least you have classes and namespaces as an organisational tool.
There's also the comp.lang.c FAQ http://c-faq.com/ , which is also very old now but still seems like a no-brainer to me.
It says something about C (I'm not sure what) that it's possible to learn quite a bit about C from a book that was written in 1994. But it was written before C99, and it shows.
If you're a C hacker and you haven't read it, definitely do. It's great.
I can call out to C libraries from other languages, so that's not a big deal.
In addition, I think that probably the JVM ecosystem is larger and that Python may be as well.
You also find certain things at the bottom (like glibc, for example) where the C ecosystem isn't as diverse as you think it is.
Here’s an excellent book: C Programming: A Modern Approach, by King.
Avoid online tutorials, and know that there are many poor books on C, including the recommended here “Learn C the Hard Way”. It has factual problems; see under “Stuff that should be avoided” here: http://iso-9899.info/wiki/Main_Page
Note also, that unlike some other languages, C is not a language that you should learn by “trying things out”, due to the nature of “undefined behavior”.
Another recommendation would be to ask for CR on IRC.
Good luck!
If at some point you want to really become a master, then switch to the actual standard.
I disagree. You have to learn the ways in which things fail in any language you learn, especially a language like C.
If you want to know how things can fail (or, what things can fail in unspecified ways), you have to do a bit of reading and no amount of trying and experimenting will conjure that knowledge.
One of them was certainly this one: https://devblogs.microsoft.com/oldnewthing/20140627-00/?p=63...
I had to start coding for work in C and without some good footing it was frustrating dealing with older code and creating new things with my very limited experience. (I had c++ which isn't the same.)
Having spent a week going a book and getting up to speed helped greatly and increased my enjoyment, reduced my frustration fwiw.
Head First C by Griffiths and Griffiths
Expert C Programming: Deep C Secrets by van der Linden
The first is an excellent introduction, especially if you treat it as a workbook.
The second is a great intermediate book.
Really advanced stuff comes from seeing things in the wild.
All the same best practices apply, but the underlying OS is more simple and the constraints, like missing floating point, forces you investigate many aspects of C.
You can use Bebbo's m68k cross compiler to compile for the platform: https://github.com/bebbo/amiga-gcc
No single source of truth for C best practices exist, but I can recommend using static analyzers like:
and
http://cppcheck.sourceforge.net/
to steer you in the right direction.
Also, compile your code with the following gcc options: -pedantic -std=<c99 or c89> -ggdb3 -O0 -Wall -Wextra -Wformat=2 -Wmissing-include-dirs -Winit-self -Wswitch-default -Wswitch-enum -Wunused-parameter -Wfloat-equal -Wundef -Wshadow -Wlarger-than-1000 -Wunsafe-loop-optimizations -Wbad-function-cast -Wcast-qual -Wcast-align -Wconversion -Wlogical-op -Waggregate-return -Wstrict-prototypes -Wold-style-definition -Wmissing-prototypes -Wmissing-declarations -Wpacked -Wpadded -Wredundant-decls -Wnested-externs -Wunreachable-code -Winline -Winvalid-pch -Wvolatile-register-var -Wstrict-aliasing=2 -Wstrict-overflow=2 -Wtraditional-conversion -Wwrite-strings
Not that they all necessarily always makes sense, but they will reveal weak points in your code.
C is fantastic language because it is within your reach to go and understand all small details of how various features are actually implemented. I find it very helpful to be able to understand how a piece of code actually functions on a low level.
Another tip: you are used to having high level features on your disposal. Don't try to duplicate those features. Figure out how actual C projects deal with program structure and flow issues that you are used to solving with high level constructs.
Also do it on a Raspberry Pi, it will allow to touch that low level better. Only flashing an LED through GPIO pins and is enough to grasp the basics.
https://www.amazon.com/Programming-Language-2nd-Brian-Kernig...
The best thing you can do is finding some open source code that interest you, read it and write it.
For example, I write my own controllers for CNCs, and there are lots of code for controlling stepper motors out there. I also learned OpenGL and sockets this way long time ago.
On the other hand, C is a terrible language for any big project unless you automate it some way.
I use mostly lisp in order to write C code automatically. C is too low level for writing any substantial code. Lisp has things like Macros(nothing to do with c macros) that can make you write code 10x, to 50x faster easily.
Do people not realize that projects like Linux and pretty much every car's ECU software (pretty "substantial" in my opinion) is written in C before claiming this? I'm not saying these software are 100% bug free, but this claim isn't accurate either. You _can_ write substantial C code when you have a good understanding of the tooling around it. Mere learning the syntax isn't enough.
It's such a crackup, because the number of developers who haven't got the memo on this is really staggering. How many lines of code is GTK? Or Postgres? Or Nginx? Never mind languages and operating systems.
I suppose Vim (~335,000 lines) or git (in the 200,000 range) is not "large" in comparison to some things, but I suspect that's actually the kind of number people think is "too big for C."
People also seem not to realize that the reason that Python library is so fast, is because it's actually wrapping C code. And that code is very often not doing some frighteningly low-level thing, but just doing ordinary stuff.
Has reading source code in a language that’s unfamiliar to you shown to be of any real benefit to learning? It seems you need at least a little bit of foundational experience with it before your brain can even parse what you’re seeing in a beneficial way.
Absolutely! You start finding idiomatic patterns and "oh, so that's how they do it" kind of things. Find library functions you never knew about but now you do. Find weird things, and look them up in a manual/reference/SO/chatroom. Things that you might find in a book, but a book that covers all the idioms and practice and weird things is gonna be as thick as the bible, and it'll get outdated (it doesn't help that most books are focused on teaching the basics rather than showing off how to architect your application well). Things that you can learn the hard way by why not look and see and learn?
> It seems you need at least a little bit of foundational experience with it before your brain can even parse what you’re seeing in a beneficial way.
Nah, you just need programming experience (in a similar paradigm) in general. A lot of what you learned from languages before will translate.
Also OP mentioned that "I can write C" and "I've started reading 'Modern C'", so it's not like they're looking for their first Hello World snippet.
OP said they've got 15+ years of programming experience. At that point, picking up a new language is all about learning the vocabulary and idioms, plus the few unique or tricky or quirky things that don't show up in other languages (or that aren't obvious from looking at code). The fastest way to get to that vocabulary is to look at real code.
Yes, I think so. The parent has experience with other languages, like python, javascript and go. C has a very similar paradigm. If it were clojure it would be different.
I find exposition to new languages real usage the best way to learn them. I went to the UK to learn english, Germany to learn German, could be painful at first, but works, specially if you know a little about what is all about.
Your brain makes sense of everything by itself in a magical way. That is the way you learn your native language.
It _can_ help no doubt but not sure that's the best way to start. Especially with a language like C. It's not so straight forward without some training or CS education.
;-)
Also, understand the undefined behavior, the compiler optimizations and how it can affect your code if you forget about the UB or if you ignore the compiler warnings, and read about the `volatile` keyword.
And a personal tip, please tell the compiler not to use strict aliasing (I think it's stupid) - you will get rid of some UB without much sacrifice.
PSA: volatile generally doesn't do what you want it to; you almost never want it. volatile does not "fix" undefined behavior.
> And a personal tip, please tell the compiler not to use strict aliasing (I think it's stupid) - you will get rid of some UB without much sacrifice.
This prevents the compiler from performing a number of optimizations.
The hardware representation of an int isn't part of C either but it most certainly had an effect on how your program will run.
Depending on how deep you want to go. The most in depth way to understand C is to learn assembly on an ISA first.
It’s one of the best books in the world. About anything. Ever.
That might be the case if you plan to use the Microsoft C compiler as they have publicly said the don't have plans to update their C compiler.
However that is not your only option, since clang and several GCC ports will also work on Windows.
Still pretty incomplete last I checked but portability is better than it was.
MSVC is probably pretty close to C99 these days. Designated initializers are C99, for example.
I'm writing a book on C. I don't think it will ever be finished or see publication. I recommend writing a book yourself - it's a great way to learn, and maybe you're a better writer than me and will publish it.
http://iso-9899.info/wiki/Books#Learning_C
Read some and do the exercises. You'll learn to write portable code to a standard rather than stuff that "accidentally works." There's a lot of crap C code in the world, full of GNUisms, overzealous macros, header-only nonsense abusing translation units, and copypasta. Don't pick up bad habits from that stuff.
Really? Which part in the book specifically if you can cite the passage.
[1]: https://www.amazon.com/C-Programming-Modern-Approach-2nd/dp/...
TBH, I think the best way to learn C is to tinker a bit with assembly first (it doesn't have to be a modern assembly dialect, just write a few 6502 or Z80 assembly programs in a home computer emulator). Coming from "below" instead of "above" C's abstraction level helps understanding the design rationale behind C, and the trade-offs between flexibility and unsafe memory handling.
But... I quickly switched to Rust as I see it being the C of the future as it continues to develop. That's just me though. (I have similar opinions towards things like Kotlin or Go.)
The books I am reading in the comments below are great resources.
The problem with C is that it's extremely dangerous. Besides learning how to program in C, you'll really want to know how to use memory debuggers such as valgrind and Dr. Memory (https://www.drmemory.org/), as well as AFL for fuzzing, and various GCC and clang sanitizers. You'll really want to let functional programming language experience inform your approach to C API design and implementation.
C support is done to the extent of ISO C++ requirements.
For those that still want to use plain old C on Windows, they have contributed to clang, and it is now available as part of Visual Studio installer.
It's a very small language with almost no batteries included, built very much around the manipulation of pointers and memory more than just about anything else.
To do C the "right" way means approaching problems from that perspective and building mountains out of toothpicks while thinking about how the billions of toothpicks need to interact with one another so they don't crash/catch fire/etc.
I wrote C for 10+ years (mostly bare metal FW), yet I am still amazed of how little I know about it. Recently for example I learnt of all the things the dynamic linker does in Linux, how symbols memory addresses are resolved at runtime using PLT, ....
The good point about C is that it can be used for very different kind of projects, so the choices are a lot.
What did you want to do on Windows? I think this depends as much on the compiler as the code.
I'll also recommend C++ FQAs [1]. It's C++, yeah, but it's relevant to C in many places, and since a lot of systems programming now uses C++ as the lingua franca, it's a good, entertaining resource.
And practice pointers. Get really comfortable with how memory works.
For example, I’m trying to learn Linux drivers or embedded programming and I thought that I’d find in a C book information about registers and such, but registers don’t really belong to the C domain but to the hardware domain.
Thus, maybe find a way to clarify what you want to use C for and then learn those domain problems and how C solves them.
I've been working on a reasonably large (and cross platform) project in C (https://domeengine.com) for a couple years now, and I find that C generally requires a certain kind of discipline. When you interact with an API, you need to read the docs, figure out their specific preconditions, and requirements on handling frees, establishing what you own and what you don't.
It also helps to make sure that if you allocate something, you clean up after in a clear and well defined way.
You should also get very familiar with debuggers (LLDB/GDB) and other dev tools, to help when you have made a mistake.
In the modern era, you have a couple of options for getting it to run on Windows. C89 using Visual Studio, The MSYS2/MinGW2 gcc toolchain, or a gcc toolchain using the WSL. I use the MSYS2 tools for DOME because it requires the fewest platform-specific modifications to my code, but this varies by usecase.
c99 is fully implemented in windows with msvc (2015), gcc, clang, and intel's compiler, so "right way" should not need to involve c89. most are c11 compliant as well.
https://www.youtube.com/channel/UCcabW7890RKJzL968QWEykA https://cs50.harvard.edu
`-Wall -Werror -pedantic` and then one of `-std=c89` or `-std=c99`
(Or equivalent in whatever Windows C compiler)
1. C: A Reference Manual, by Harbison and Steele. https://www.amazon.com/Reference-Manual-Samuel-P-Harbison/dp...
2. The C Puzzle Book, by Alan Fueur. https://www.amazon.com/Puzzle-Book-Alan-R-Feuer/dp/020160461...
Harbison and Steele has much better explanations than K&R. The Fueur book taught me a lot about C declarations. Declarations are that part of C language that is them most unnecessarily difficult.
You asked about a slightly different question, best practices. But in the real world you'll run into a lot of code that practices below that level.
As a reference, I like QEMU's source code[1]. It's huge, but the style and practices found in any file will help you get a grip on good C.
The definition in the C standard of a possible expansion of the macro NULL is quite loose; it just has to be a null pointer constant. Therefore, a C compiler could choose any of the following for it: 0U, 0, '\0', 0UL, 0L, 0ULL, 0LL, or (void * )0. It is important that the type behind NULL is not prescribed by the C standard. Often, people use it to emphasize that they are talking about a pointer constant, which it simply isn’t on many platforms. Using NULL in a context that we have not mastered completely is even dangerous. This will in particular appear in the context of functions with a variable number of arguments.
NULL hides more than it clarifies. Either use 0 or, if you really want to emphasize that the value is a pointer, use the magic token sequence (void * )0 directly.
https://www.gnu.org/software/libc/manual/html_node/Null-Poin... says:
The preferred way to write a null pointer constant is with NULL. You can also use 0 or (void * )0 as a null pointer constant, but using NULL is cleaner because it makes the purpose of the constant more evident.
If you use the null pointer constant as a function argument, then for complete portability you should make sure that the function has a prototype declaration. Otherwise, if the target machine has two different pointer representations, the compiler won’t know which representation to use for that argument. You can avoid the problem by explicitly casting the constant to the proper pointer type, but we recommend instead adding a prototype for the function you are calling.
https://man.openbsd.org/style says:
NULL is the preferred null pointer constant. Use NULL instead of (type * )0 or (type * )NULL in all cases except for arguments to variadic functions where the compiler does not know the type.
---
Readers: what is your take on it and why?
It's ok to draw a line on what implementations you care about. And honestly, at this day and age, I couldn't care less about implementations that define NULL to be 0 (or something else that isn't a pointer). It's a case of where it's not worth everyone's time to be catering for the lowest common denominator.. instead, fix that implementation or just go download a better one.
If I could make breaking changes to C today, I would remove the NULL macro and replace with a lowercase null (or nil or whatever) keyword a definition that doesn't impose pointless burden on its users.
That said, I'm still very much in the habit of casting NULL to void* when I'm using it as an argument for variadic functions. It's silly, but that's just how we do things...
All the bad stuff that people on both sides of this argument predict is going to happen somewhere in some code base, precisely because people on both sides can't agree and think the world would be better if everyone agreed on those people's personal aesthetic ideals.
So... don't sweat it. Conform to whatever code base you're working on has decided, and if it's inconsistent work to make it so.
And above all, no matter what else: Don't start dumb fights over this nonsense. Yeesh.
What I'd suggest is, learn syntax and try to understand linux kernel (which is one of the very well crafted software piece we have written in C). If you don't want to go that much of a deep dive, you can check sqlite source code as well. Writing code starts with reading it. Do yourself a favour and spend more time on reading code than reading books.
Crypto library in the size of a hundred tweets [ https://twitter.com/tweetnacl ] by the only person with a genuine claim to being able to write safe C & company.
* https://github.com/mit-pdos/xv6-public
&
* https://github.com/mit-pdos/xv6-riscv
UNIX v6 clone in ANSI C by influential Plan 9-era Bell Laboratories employee and now influential Google employee Russ Cox, along with influential computer virus author and son of one of the original UNIX authors Robert T. Morrison; entire source code fits in under a hundred pages of well-typeset documents [ warning, old copy, you should generate a modern one: https://pdos.csail.mit.edu/6.828/2011/xv6/xv6-rev6.pdf ].
It also helps to build up (and refactor) your toolset over time, memory handling wrappers, logging, I/O, daemonize (https://github.com/jirihnidek/daemon) etc, so that you don't have to keep reinventing the wheel.
if I'd recommend one book then it's: https://en.wikipedia.org/wiki/Advanced_Programming_in_the_Un...
Here are my two cents.
I have been programming in C professionally for the past 12 years, and I think the way to go would be to go implement a well known tool in C and then compare your code with the open source code of that tool. e.g. Git.
You will learn a lot from how the feature that you chose to implement in a certain way was implemented in a completely different way for various reasons.
You will always be vulnerable to criticism and other people's opinions, and you need to open up and acknowledge this fact and work with that. There are best practices and principles you can adhere to. Stand up for your design decisions, and refactor when you have to.
My advice: think about what kind of software you want to write and look for similar projects, libraries, or contribute with a new module, functionality, adapt something to your needs...
I write in several languages and read lots of C projects made by others and find the affirmation hard to believe: C is so simple that is very easy to read if the writer of code has a minimum of competency.
You can say that of projects like C++, specially things like C++11 that are languages by committee so complex that you can affirm that every C++ programmer is different or find programmers that could not understand each coder's code. Not so with C.
That happens to me with C++ and lisp code. It takes a while for me to understand what the author uses(or abuses) before being able to understand the code. That does not happen with C code.
But it is perfectly possible to use C as a language with a more modern and easier to read API, but you'll probably have to build your own.
stdint.h is useful and should be required reading. I still come across too many C projects that reimplement it poorly.
Learn to use valgrind, and maybe ddd too.
Read "Who Says C is simple?": http://cil-project.github.io/cil/doc/html/cil/
Take up a different language :-)
I think the primary topic to master in C is pointers. This is where most falter. It takes a few years to "master" (if we ever do). Here I would recommend "Understanding and Using C Pointers", Richard Reese. [2]
If you are interested in networking, any of the classic "TCP/IP Illustrated Vols I/II/III", W. Richard Stevens, [3] contain a ton of C code to implement components of TCP/IP.
If you are interested in Graphics, then "Graphics Gems", Andrew Glassner [4] is a good source.
"An Introduction to GCC", Brian Gough, [5] to understand the tooling and its various bells and whistles.
My learning swimming by jumping into the deep end of the pool experience was realized by learning Windows Programming using the Charles Petzold book and navigating through Microsoft Foundation Classes in the late 80s/early 90s. The state of the art in tooling wasn't that great in those days and I spent months with the book to get things going. This was done after I had built a foundation with K&R and a decent amount of Unix network programming.
I see a lot of the other posts recommend more modern books. But you still need to build your foundation on C and Pointers in particular.
Good luck on your journey.
[1] https://www.amazon.com/Programming-Language-2nd-Brian-Kernig...
[2] https://www.amazon.com/Understanding-Using-Pointers-Techniqu...
[3] https://www.amazon.com/TCP-Illustrated-Protocols-Addison-Wes...
[4] https://www.amazon.com/Graphics-Gems-Andrew-S-Glassner/dp/01...
[5] https://www.amazon.com/Introduction-GCC-GNU-Compilers/dp/095...
In fact, I read couple of chapters in Modern C yesterday :). Here are some of the things I am doing to improve my C skills to match with some of the admired/professional developers.
Decide which platform to use
~~~~~~~~~~~~~~~~~~
Unfortunately, to become proficient in it we need to write code and focus on a platform. I have been fighting between whether to develop on Windows vs Linux. I am very experienced in Windows environment(using debuggers/cl/linkers/Windbg etc) but when it comes to writing good quality C code(not C++) and for learning how to write good maintainable moderately large source code, my research showed that Windows compilers/C standard APIs are not great, in fact they hinder your productivity. I have wasted countless number of hours to just figure out how to properly create a simple C project with a decent build system. Unfortunately, I could not find one. The closest I could find is CMake as MSBuild is a nightmare to work with. I even tried NMAKE but failed. When it comes to documentation of finding basic C Api usage, MSDN(https://docs.microsoft.com/en-us/cpp/c-runtime-library/run-t...) does a decent job. But in the name of security you will find zillion variations(_s, _l) of an API and by default msvc compiler will not let you use some of the API in their simple forms. Instead, you have to define _CRT_SECURE_NO_WARNINGS etc. I think for someone just getting started to develop/learn to write a decent code base in C these restrictions really hinder the productivity. So finally, I have decided to instead focus my learning on Linux platform(currently through WSL - Windows subsystem for Linux) with its POSIX apis. You know what, `man 3 printf` or `man 3 strlen` is soooooo much better than googling msdn
Mastering C
~~~~~~~
I think, the simple and straight answer here is reading good code and writing "good" code and also reading good C content(be it books or articles). I think these are the three ingredients necessary to get started. Of all the open source projects that I have investigated, I found Linux Kernel and related projects seems to have very good taste in terms of code quality. Currently, I am just focused how they use the language rather than what they actually do in the project. Things like, how they structure the project, how they name things, how they use types, how they create structures, how they pass structures to function, how they use light weight object based constructs, how they handle errors in function(for example forward only goto exits), how they use signed/unsigned variables etc(more of my learnings to the end), how they use their own data structures. I think its good to initially focus/target on ANSI C API with C99 instead of heavily relying on the OS specific API on which ever platform you choose. For example, such projects could be writing binary file parsers for example projects like .ISO file format etc.
Good C projects/articles
~~~~~~~~~~~~~~~
1. Winlib.net - https://github.com/jcpowermac/wimlib is a great source of information
2. e2fsprogs - https://git.kernel.org/pub/scm/fs/ext2/e2fsprogs.git/
3. MUSL - https://git.musl-libc.org/cgit/musl/tree/
4. General C Coding Style - https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/lin...
4. https://nullprogram.com/tags/c/ - great source of C knowledge
5. CCAN - https://github.com/rustyrussell/ccan/tree/master/ccan - great source of C tidbits from none other than Rusty Russell - I haven't read all of them
6. POSIX 2018 standard - https://pubs.opengroup.org/onlinepubs/9699919799.2018edition...
continued in the comment....
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1. _t suffix is the notion used to denote a typedef for a given type /* This example is from do_journal.c in e2fsprogs / struct journal_transaction_s { ... blk64_t start, end; ... }; typedef struct journal_transaction_s journal_transaction_t;
2. Know about headers like stddef.h and stdint.h and when they are supposed to be used. for example: When to use normal data types like int vs int16_t etc.
3. From https://en.cppreference.com/w/c/types/integer we can sense that int_t are of exact width types which might have some perf side effects if the underlying hardware does not support the width natively.
For example, in visual studio x86/x64 we have typedef short int16_t; and
typedef int int32_t; int_fast_t* on the other hand make sure a suitable
width which maps natively to the available hardware type For example, in
visual studio x86/x64 typedef int int_fast16_t; instead of typedef short
int_fast16_t; size_t on the other hand alias to the natural unsigned word
length of the hardware for example on x86 typedef unsigned int size_t; and
on x64 typedef unsigned __int64 size_t;
4. Know your compiler predefined standard macros
On Microsoft compiler
_WIN64 - defined when we are compiling for x64 code
_WIN32 - defined when both x86 and x64 code is getting compiled
_MSC_VER - defines which compiler version are we using, indicate different visual studio versions
__cplusplus - defined when the translation unit is compiled as C++5. We can get a FILE* from HANDLE using below APIs from io.h and fcntl.h Fd = _open_osfhandle((intptr_t)Handle, _O_TEXT); File = _wfdopen(Fd, L"r"); Once we get the FILE* we can use fgets for line oriented string operations
6. Learned about var args and aligned memory Aligned memory means the address returned by the _aligned_malloc is always divisible by the alignment we specify. For example: char p = _aligned_malloc(10, 4); the address return in p will be always be divisible by 4. We should also free the allocated memory using _aligned_free(p)
7. atoi(str) this api also processes input string until it can convert. For example atoi("123asda") will still give 123 as the return result. Any whitespace in the beginning of the input string will be ignored. So atoi(" 123asd") will still return 123. It is recommended to use strto functions to convert strings to int/long/float types as they also can return pointer to the character which is a non integer
8. UCRT support around 40 POSIX system level APIs but most of these have _ prefix to them. wimlib in wimlib_tchar.h defines #define topen _open for Win32 and #define topen open for POSIX systems The take away here is the UCRT implementation even though differ in name the parameters are exactly the same.
For example:
UCRT Win32: int _open(const char *filename, int oflag, int pmode);
POSIX: int open(const char *pathname, int flags, mode_t mode);
9. We can install only build tools(VC compiler) excluding IDE from https://aka.ms/buildtools10. Best video on C Standard and some of its less known features - "New" Features in C - Dan Saks Year C Standard Comments 1983 C standard committee is formed 1989 C89 C89 US standard 1990 C90 C89 International Standard 1999 C99 C99 Standard 2011 C11 C11 Standard 2018 C18 C18 Bugfix release
_reserved - Reserved for global scope. But we can use any identifier with an
_ as a local variable or a structure member
__reserved - Always reserved. Meaning the user program should not use any
variable with two underscores __
_Reserved - Always reserved. Meaning the user program should not use any
variable with an underscore and capital letter.
This is the reason why _Bool is named that way to prevent breaking existing
bool typedef used in existing code.
11. Another good video on lesser known C features - Choosing the Right Integer
Types in C and C++ - Dan Saks - code::dive 2018 we can use CHAR_BIT from limits.h instead of 8 for example when you want to
print the bits in a integer, we can do below `for (size_t i = sizeof(int) *
CHAR_BIT; i >= 0; i--) {...}`
12. size_t denotes the native architecture supported natural word size. So for
32bit it is 4 bytes unsigned quantity and for 64bit it is 8 bytes unsigned
quantity. Hence it is defined as follows #ifdef _WIN64
typedef unsigned __int64 size_t; //8 bytes on x64
#else
typedef unsigned int size_t; //4 bytes on x86
#endif
where as uintmax_t denotes the maximum integer type that is available in the
language. So on a 32bit you could still represent a 64 bit quantity using
long long even though it not what the architecture directly maps to. So
below is how it is defined in both x86 and x64
typedef unsigned long long uintmax_t; //in MSVC both x86 and x64 support 64
bit quantities using long long. So size_t does not give us the maximum
unsigned integer, instead it gives us the native unsigned integer i.e., on
x86 it will be 32bits and on x64 it is 64bits. So recommendation is to use
size_t where ever possible instead of using int. for example.
int len = strlen(str); // not recommended because on both x86 and x64 of MSVC int is 4 bytes due to LLP64
size_t len = strlen(str); // recommended because size_t will automatically maps to 4 bytes in x86 and 8 bytes in x64
13. C11 introduced the concept of static asserts. These are basically
conditional asserts which can be evaluated during compile time. So C11 has a
new keyword called _Static_assert(expr, message) The reason for this ugly
name is the same idea of not to break existing code. so for convenience
assert.h header provides static_assert macro which mean the same. One of the
use of static asserts is below struct book {
int pages;
char author[10];
float price;
};
static_assert(sizeof(struct book) == sizeof(int) + 10 * sizeof(char) + sizeof(float),
"structure contains padding holes!");
14. Another good video on some low level details - Storage Duration and Linkage
in C and C++ - Dan Saks15. #define _CRT_SECURE_NO_WARNINGS can be used to disable CRT warning for common functions.
16. Any ucrt function which begins with _ is a non standard api provided by ucrt. For example in string.h's _strdup, _strlwr, _strrev are some. The take away here is, it is easy to identify which function is part of C standard and which are not. Interestingly some(not all) of these non standard functions are part of posix so in glibc(which implements posix) don't have _ in them.
17. All posix function in posix standard with [CX] annotation indicate Extension to the ISO C standard for example, below function from stdlib.h is posix extension. UCRT defines a similar api called _putenv, since this is not part of C standard, UCRT version has an _
stdlib.h - posix
[CX] int setenv(const char *, const char *, int);
stdlib.h - ucrt
int _putenv( const char *envstring );
stdio.h - posix
[CX] int fileno(FILE *);
stdio.h - ucrt
int _fileno( FILE *stream );
18. Learned about CGold: The Hitchhiker’s Guide to the CMake. An awesome
tutorial about CMake. Now it is super easy to start a C project without
worrying about the individual build systems. # CMakeLists.txt - minimum content
cmake_minimum_required(VERSION 3.4)
project(command_line_parser)
add_executable(command_line_parser main.c)
# commands to run to generate the respective native build files like vcxproj files
# In below command -S standards for source directory path.
# -B stands for final directory where vcxproj files are generated
# CMake only generate one flavor (x64/x86) per project file, here we are generating x64 by specifying x64
cmake -S . -B builds -G "Visual Studio 16 2019" -A x64
# we can also use cmake-gui to do the above
# Once vcxproj files are generated we can either directly build the proj files using Visual Studio
# or better use cmake itself to build it for us from CMD using msbuild
cmake --build builds
Hope these help.This is undefined behavior in POSIX.
Honestly, I think any programmer writing C today should have it around for reference. And yeah you kinda need to read it too, or you won't be able to refer much.
That said, no need to read it cover to cover. There's stuff one can earmark as being there but ignore until it's actually needed (for example: the grammar and all the library functions).
And speaking of tedu, I would recommend to the OP that they get in the habit of checking out the OpenBSD man pages for libc functions.
It's broken up into exercises that you start working through straight away, and you start early with valgrind.
But, for your own sanity and everyone else's, do not start new projects in C! (Aside from purely academic ones for you to learn.) The point of a programming language is to help humans write safe and correct code. In this sense, C has completely failed. The syntax itself is just begging you to make a subtle mistake. And due to lack of memory safety, even the most well tested and scrutinized projects in C/C++ (such as Chromium) suffer from buffer overflows or memory leaks, potential security vulnerabilities that are automatically prevented by design in other languages. If you need to do something low level with any sort of confidence, use a memory-safe language like Rust, which can even do interop with existing C libraries and code.
(Edit: typo)
C's syntax is fine (if a bit ugly around declarations), it's really the memory safety that gets you.