Principles for C Programming
drewdevault.com
drewdevault.com
> Do not use a typedef to hide a pointer or avoid writing “struct”.
Untagged structures:
typedef struct { ... } my_struct_t;
are pretty standard practice in C. In my case, I don't even use tagged structs unless the struct is self-referential: typedef struct __my_struct_t {
struct __my_struct_t* link;
} my_struct_t;
And some examples in the Linux Source:https://github.com/torvalds/linux/blob/9331b6740f86163908de6...
https://github.com/torvalds/linux/blob/6cdc577a18a616c331f57...
And FreeBSD:
https://github.com/freebsd/freebsd/blob/0ee2302122d0710ced1e...
But depending on the desired intent one would be preferable to the other.
for example a typedef would be used to hide the complexity for an opaque pointer.
Do not use a typedef to hide a pointer
Do not use a typedef to avoid writing “struct”
Nothing to do with opaque pointers directly.And considering it's titled "Principles for C programming", it would seem intended as some sort of ruleset.
I think some of the contortions around this might come from people who are afraid of polluting global namespaces. But struct tags and other names live in different namespaces. Your second example could just as well (and without using a __ prefix, which is reserved for the implementation and therefore, strictly speaking, is undefined behavior in user code) be:
typedef struct my_struct_t {
struct my_struct_t* link;
} my_struct_t;
or even struct my_struct_t {
struct my_struct_t* link;
};
typedef struct my_struct_t my_struct_t;
Arguably, if you "own" the my_struct_t type name, you also have a right to declare "struct my_struct_t".Sure, but that's a preference. Which was my point. I'm not against people tagging structs, but I'm also not against typedef'd untagged structs. And clearly the C community at large isn't either.
> But struct tags and other names live in different namespaces.
I'm aware of where tags live, I just see no necessity in defining something that will never be used.
> Your second example could just as well (and without using a __ prefix, which is reserved for the implementation and therefore, strictly speaking, is undefined behavior in user code) be:
This is pedantry and off topic. Name it whatever you like. It can be zyzzyx, for all I care; it's an internal tag at this point. You're correct about the __, that was intended to be _; and is simply a preference on my part. I'm not evangelizing it's use.
> Arguably, if you "own" the my_struct_t type name, you also have a right to declare "struct my_struct_t".
That's not the point. I will never call "struct my_struct_t" outside of a self-referential context. So it's pointless. I could define a tag. I could not. My code will compile the same.
Actually, sometimes it is best to have a fixed size buffer, sized appropriately for the maximum data-bytes allowed as per design. Especially true for embedded systems where dynamic allocation is usually frowned upon.
This means that if you are coding C and using buffers, good code will necessarily look and feel unnatural. You should be deeply suspicious of any code you find managing buffer contents that has nothing awkward-looking. Managing buffers correctly in C takes far above average care and attention. That care needs to leave tracks to show it happened, and to remind subsequent maintainers to exercise it too.
As an initial and trivial matter, using macros and defining new macros are different things. It's absurd to say you can't use macros, and comparison to ((void *)0) rather than to NULL is far less readable. Using macros defined by the language is incontrovertibly normal, expected, and the Right Thing.
As far as defining new macros, though, I still think there are very defensible reasons to use them, especially for defining a macro that expands to debug code when a debug constant is defined, and expands to nothing when that same debug constant is undefined.
#ifdef _MY_DEBUG
#define ENTER 1
#define EXIT 2
#define TRACE(x) \
fprintf(stderr, "%s function %s\n", (x == ENTER) ? "Entering" : "Exiting", __func__)
#else
#define TRACE(x)
#endif
Forgive any errors above, but that's pretty straightforward to follow and can make debugging a lot easier by giving a naive sort of stack trace on stderr. Add TRACE(ENTER) and TRACE(EXIT) at the beginning and end of functions and you can figure out where a segfault or whatever is happening, and it all gets defined away in non-debug code.I started to use C and like I said only recently, with my eyes wide open. There are faults and foot-guns but the simpler you keep things the better. For me it's less about performance, even though naive C will likely be quicker than most dynamic languages out there (where most of my experience is) and more about manual memory management and much smaller memory usage. Another reason I like C is that it is available everywhere. Of course as the article mentions ("Use only standard features.") you need to take care of what is included for it to be available on other platforms easily or at all. I'm saying this as someone who is only targeting x86 desktops and servers rather than targeting other processor architectures, so I'm mainly thinking about different OSs. From the viewpoint of Linux and the BSDs on which I have some experience to talk about, as they are primarily in C, the whole stack is C and what I mean is that if you know C you can can potentially figure out how everything works and is put together all the way down to the kernel. I also can't overestimate just how good man pages are in Linux and BSDs for C functions, using them is probably one of the best learning experiences for C that I've found.
[1] https://8bitworkshop.com/v3.4.2/?platform=vcs&file=examples%...