I haven't written actual C code in decades. Did C11 get magic statics with thread-safe initialization like C++11? Does C even have non-trivial initialization of statics local variables?
C does have initialization of local variables, including static.
It does not have any thread safety in standard for these, however, so no thread safe singletons. (You can get them initialized by linker or runtime safely, of course, e.g. ELF .bss and UCRT on Windows)
You can use atomics to implement such a singleton if they are available.
This is one of the main reasons why C is more portable than C++.
I don't know why it would be magic to have thread-local storage. Thread-local variables are either static or extern. Thread-local static variables are initialized like normal static variables (initialization on declaration line occurs on first instantiation), but with a separate copy per thread.
N1570, sec. 6.2.4, para. 4: An object whose identifier is declared with the storage-class specifier _Thread_local has thread storage duration. Its lifetime is the entire execution of the thread for which it is created, and its stored value is initialized when the thread is started. There is a distinct object per thread, and use of the declared name in an expression refers to the object associated with the thread evaluating the expression.
N1570, sec. 6.7.9, para. 10: If an object that has static or thread storage duration is not initialized explicitly, then [it is initialized to NULL or 0 as appropriate, including all-bits-zero padding in structs]
Not thread local storage. Plain function statics are initialized on first use in c++ (if the initializer is not trivial), and this initialization is thread safe. There is a simple algorithm to implement it that has very little overhead on the already initialized case that is known as magic statics (it is basically an instance of the double checked locking pattern).
Thanks for submitting this. I'm teaching myself C so these high level overviews are super useful for improving my intuition.
In the following example, shouldn't there be an asterisk * before the data argument in the getData function call? The way I understand it the function is expecting a pointer so you would need to pass it a pointer of the data object.
>
"If you want to “return” memory from a function, you don’t have to use malloc/allocated storage; you can pass a pointer to a local data:
No, it's correct. The asterisk is a little inconsistent, in that it means two opposite things. In the declaration it means "this is a pointer." However, in an expression, it means "this is the underlying type" and serves to dereference the pointer.
int a = 5;
int *x; // this is a pointer
x = &a;
int c = *x; // both c and *x are ints
If it were *data, it would be equivalent to *(data + 0), which is equivalent to data[0], which is an int. You don't want to pass an int, you want to pass an *int.
I don't know for certain, but I suspect it simplified the language's grammar, since C's "declaration follows use" rule means you can basically repurpose the expression grammar for declarations instead of needing new rules for types. This is also why the function pointer syntax is so baroque (`int (*x)();` declares a variable `x` containing a pointer to a function taking no parameters and returning an int).
Because now you've got an int pointer and an int. The star associates with the right, not left.
I prefer to use the variant you described though, because it feels more natural to associate the pointer with the type itself. As far as I know, the only pitfall is in the multiple declaration thing so I just don't use it.
IMO, it's also more readable in this case:
int *get_int(void);
int* get_int(void);
The second one more clearly shows that it returns a pointer-to-int.
Multiple declaration is generally frowned upon, because you declare the variables without immediately setting them to something.
If you always set new variables in the same statement you declare them, then you don't use multiple declarations, which means there is no ambiguity putting the * by the type name.
So convention wins out for convention's sake. And that's the entire point of convention in the first place: to sidestep the ugly warts of a decades-old language design.
Spaces are ignored (except to separate things where other syntactical things like * or , aren't present), and * binds to the variable on the right, not the type on the left. I actually got this wrong in an online test, but I screenshotted every question so I could go over them later (! I admit, a dirty trick but I learned things like this from it, though I still did well enough on the test to get the interview).
int*x,y; // x is pointer to int, y is int.
int x,*y; // x is int, y is pointer to int
And the reason I got it wrong on the test is it had been MANY years since I defined more than one variable in a statement (one variable defined per line is wordier but much cleaner), so if I ever knew this rule before, I had forgotten it over time.
I keep wanting to use slash-star comments, but I recall // is comment-to-end-of-line in C99 and later, something picked up from its earlier use in C++.
Oh yeah, C99 has become the de-facto "official" C language, regardless of more recent changes/improvements, as not all newer changes have made it into newer compilers, and most code written since 1999 seems to follow the C99 standard. I recall gcc and many other compilers have some option to specify which standard to use for compiling.
I think the question is why it binds to the variable rather than the type. It's obviously a choice that the designers have made; e.g. C# has very similar syntax, but:
int* x, y;
declares two pointers.
I think the syntax and the underpinning "declaration follows use" rule are what they got when they tried to generalize the traditional array declaration syntax with square brackets after the array name which they inherited directly from B, and ultimately all the way from Algol:
int x, y[10], z[20];
In B, though, arrays were not a type; when you wrote this:
auto x, y[10], z[20];
x, y, and z all have the same type (word); the [] is basically just alloca(). This all works because the type of element in any array is also the same (word), so you don't need to distinguish different arrays for the purposes of correctly implementing [].
But in C, the compiler has to know the type of the array element, since it can vary. Which means that it has to be reflected in the type of the array, somehow. Which means that arrays are now a type, and thus [] is part of the type declaration.
And if you want to keep the old syntax for array declarations, then you get this situation where the type is separated by the array name in the middle. If you then try to formalize this somehow, the "declaration follows use" rule feels like the simplest way to explain it, and applying it to pointers as well makes sense from a consistency perspective.
"integer pointer" named "x" set to address of integer "a".
---
As a sibling comment pointed out, this is ambiguous when using multiple declaration:
int* foo, bar;
The above statement declares an "integer pointer" foo and an "integer" bar. It can be unambiguously rewritten as:
int bar, *foo;
But multiple declaration sucks anyway! It's widely accepted good practice to set (instantiate) your variables in the same statement that you declare them. Otherwise your program might start reading whatever data was lying around on the stack (the current value of bar) or worse: whatever random memory address it refers to (the current value of foo).
I think it would help if beginners learn a language other than C to learn about pointers. My first language was Pascal, and it didn't have a confusing declaration syntax, nor did it have a confusing array decay behavior so it was much much easier to learn. Nowadays of course I don't think about it but those details mattered to beginners.
The name of an array decays to a pointer to the first element in various contexts. You could do `&data[0]` but it means exactly the same thing and would read as over-complicated things to C programmers.
(Edit: But probably not in release builds, as @rmind points out.)
> The closest thing to a convention I know of is that some people name types like my_type_t since many standard C types are like that
Beware that names beginning with "int"/"uint" and ending with "_t" are reserved in <stdint.h>.
[Edited; I originally missed the part about "beginning with int/uint", and wrote the following incorrectly comment: "That shouldn't be recommended, because names ending with "_t" are reserved. (As of C23 they are only "potentially reserved", which means they are only reserved if an implementation actually uses the name: https://en.cppreference.com/w/c/language/identifier. Previously, defining any typedef name ending with "_t" technically invokes undefined behaviour.)"]
The post never mentions undefined behaviour, which I think is a big omission (especially for programmers coming from languages with array index checking).
But wouldn't one be required to include a particular header in such case (i.e. the correct header for defining a particular type)?
I mean, no typedef names are defined in the global scope without including any headers right? Like I find it really weird that a type ending in _t would be UB if there is no such typedef name declared at all.
Or is this UB stuff merely a way for the ISO C committee to enforce this without having to define <something more complicated>?
The purpose of this particular naming rule is to allow adding new typedefs such as int128_t. The "undefined behaviour" part is for declaration of any reserved identifier (not specifically for this naming rule). I don't know why the standard uses "undefined behaviour" instead of the other classes (https://en.cppreference.com/w/cpp/language/ub); I suspect because it gives compilers the most flexibility.
One potential issue would be that the compiler is free to assume any type with the name `foobar_t` is _the_ `foobar_t` from the standard (if one is added), it doesn't matter where that definition comes from. It may then make incorrect assumptions or optimizations based on specific logic about that type which end up breaking your code.
You can declare a type without (fully) defining it, like in
typedef struct foo foo_t;
and then have code that (for example) works with pointers to it (*foo_t). If you include a standard header containing such a forward declaration, and also declare foo_t yourself, no compilation error might be triggered, but other translation units might use differing definitions of struct foo, leading to unpredictable behavior in the linked program.
The standard reserves several classes of identifiers, "_t" suffix [edit: with also "int"/"uint" prefix] is just one of several rules. Another rule is "All identifiers that begin with an underscore followed by a capital letter or by another underscore" (and also "All external identifiers that begin with an underscore").
Why the hell "potentially reserved" was introduced? How is it different from simply "reserved" in practice except for the fact such things can be missing? How do you even use a "potentially reserved" entity reliably? Write your own implementation for platforms where such an entity is not provided, and then conditionally not link it on the platforms where it actually is provided? Is the latter even possible?
Also, apparently, "function names [...] beginning with 'is' or 'to' followed by a lowercase letter" are reserved if <ctype.h> and/or <wctype.h> are included. So apparently I can't have a function named "touch_page()" or "issue_command()" in my code. Just lovely.
> The goal of the future language and library reservations is to alert C programmers of the potential for
future standards to use a given identifier as a keyword, macro, or entity with external linkage so that
WG14 can add features with less fear of conflict with identifiers in user’s code. However, the mechanism
by which this is accomplished is overly restrictive – it introduces unbounded runtime undefined
behavior into programs using a future language/library reserved identifier despite there not being any
actual conflict between the identifier chosen and the current release of the standard. ...
> Instead of making the future language/library identifiers be reserved identifiers, causing their use to be
runtime unbounded undefined behavior per 7.1.3p1, we propose introducing the notion of a potentially
reserved identifier to describe the future language and library identifiers (but not the other kind of
reservations like __name or _Name). These potentially reserved identifiers would be an informative
(rather than normative) mechanism for alerting users to the potential for the committee to use the
identifiers in a future release of the standard. Once an identifier is standardized, the identifier stops
being potentially reserved and becomes fully reserved (and its use would then be undefined behavior
per the existing wording in C17 7.1.3p2). These potentially reserved identifiers could either be listed in
Annex A/B (as appropriate), Annex J, or within a new informative annex. Additionally, it may be
reasonable to add a recommended practice for implementations to provide a way for users to discover
use of a potentially reserved identifier. By using an informative rather than normative restriction, the
committee can continue to caution users as to future identifier usage by the standard without adding
undue burden for developers targeting a specific version of the standard.
So... instead of mandating implementations to warn about (re)defining a reserved identifier, they introduce another class of "not yet reserved indentifiers" and advise implementations to warn about defining such identifiers in the user code — even though it's completely legal, — until the moment the implementation itself actually uses/defines such an identifier at which point warning about such redefinition in the user code — now illegal and UB — is no longer necessary or advised.
Am I completely misreading this or is this actually insane? Besides, there is already a huge swath of reserved identifiers in C, why do they feel the need to make an even larger chunk of names unavailable to the programmers?
The problem is that the traditional wording of C meant that any variable named 'top' was technically UB, because it begins with `to'.
In practical terms, what compilers will do is, if C2y adds a 'togoodness' function, they will add a warning to C89-C2x modes saying "this is now a library function in C2y," or maybe even have an extension to use the new thing in earlier modes. This is what they already do in large part; it's semantic wording to make this behavior allowable without resorting to the full unlimited power of UB.
> Besides, there is already a huge swath of reserved identifiers in C, why do they feel the need to make an even larger chunk of names unavailable to the programmers?
The C23 change was mostly to downgrade some of the existing reserved identifiers from "reserved" to "potentially reserved". (It also added some new reserved and potentially reserved identifiers, but they seem reasonable to me.)
I still fail to see any practical difference between these two categories, except that the implementations are recommended to diagnose illegal-in-the-future uses of potentially reserved identifiers but are neither required nor recommended to diagnose actually illegal uses of reserved identifiers. There is also no way to distinguish p.r.i from r.i.
It also means that if an identifier becomes potentially reserved in C23 and reserved in C3X, then compiling a valid C11 program that uses it as C23 will give you a warning, which you can fix and then compile resulting valid C23 program as C3X without any problem; but compiling such a C11 program straight up as C3X will give you no warning and a program with UB.
Seriously, it boggles my mind. Just a) require diagnostics for invalid uses of reserved identifiers starting from C23, b) don't introduce new reserved identifiers, there is already a huge amount of them.
In addition, I recommend -fsanitize=integer. This adds checks for unsigned integer overflow which is well-defined but almost never what you want. It also checks for truncation and sign changes in implicit conversions which can be helpful to identify bugs. This doesn't work if you pepper your code base with explicit integer casts, though, which many have considered good practice in the past.
Wow nice, I didn't know about this one. I can add some more which are less known. This is my current sanitize invocation (minus the addition of "integer" which I'll be adding, unless one of these other ones covers it):
-fsanitize=address,leak,undefined,cfi,function
CFI has checks for unrelated casts and mismatched vtables which is very useful. It requires that you pass -flto or -flto=thin and -fvisibility=hidden.
You can read a comparison with -fsanitize=function here:
I think "leak" is always enabled by "address". It's only useful if you want run LeakSanitizer in stand-alone mode. "integer" is only enabled on demand because it warns about well-defined (but still dangerous) code. You can also enable "unsigned-integer-overflow" and "implicit-conversion" separately. See https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html#...
So I actually stand by my original comment that the convention of using "_t" suffix shouldn't be recommended. (It's just that the reasoning is for conformance with POSIX rather than with ISO C.)
Well, semantically, "size_t" makes sense to me ("the type of a size variable"), while "uint_t" does not ("the type of a uint variable"), because "uint" is already a type, obviously - just like "int".
This looks decent, but I'm (highly) opposed to recommending `strncpy()` as a fix for `strcpy()` lacking bounds-checking. That's not what it's for, it's weird and should be considered as obosolete as `gets()` in my opinion.
If available, it's much better to do the `snprintf()` way as I mentioned in a comment last week, i.e. replace `strcpy(dest, src)` with `snprintf(dst, sizeof dst, "%s", src)` and always remember that "%s" part. Never put src there, of course.
There's also `strlcpy()` on some systems, but it's not standard.
strncpy does have its odd and rare use-case, but 100% agree that it is not at all a “fix” for strcpy, it’s not designed for that purpose, and unsuited to it, being both unsafe (does not guarantee NUL-termination) and unnecessary costly (fills the destination with NULs).
The strn* category was generally designed for fixed-size NUL-padded content (though not all of them because why be coherent?), the entire item is incorrect, and really makes the entire thing suspicious.
Lol no. These are the Annex K stuff which Microsoft got into the standard, which got standardised with a different behaviour than Windows’ (so even on windows following the spec doesn’t work) and which no one else wants to implement at all.
And they don’t actually “do exactly what you want”, see for instance N1967 (“field experience with annex k”) which is less than glowing.
Would it be a sin to use memcpy() and leave things like input validation to a separate function? I'm nervous any time somebody takes a function with purpose X and uses it for purpose Y.
Uh, isn't using `memcpy()` to copy strings doing exactly that?
The problem is that `memcpy()` doesn't know about (of course) string terminators, so you have to do a separate call to `strlen()` to figure out the length, thus visiting every character twice which of course makes no sense at all (spoken like a C programmer I guess ... since I am one).
If you already know the length due to other means, then of course it's fine to use `memcpy()` as long as you remember to include the terminator. :)
The reason you would want to use memcpy would be if 1) you already know what the length is, 2) if you need a custom validator for your input, 3) you don't want to validate your input (however snprintf() is doing that), 4) if the string may include nulls or there is no null terminator.
But the fifth reason may be that depending on snprintf as your "custom-validator-and-encoder-plus-null-terminator" may introduce subtle bugs in your program if you don't know exactly what snprintf is doing under the hood and what its limitations are. By using memcpy and a custom validator, you can be more explicit about how data is handled in your program and avoid uncertainty.
(by "validate" I mean handle the data as your program expects. this could be differentiating between ASCII/UTF-8/UTF-16/UTF-32, adding/preserving/removing a byte-order mark, eliminating non-printable characters, length requirements, custom terminators, or some other requirement of whatever is going to be using the new copy of the data)
> -Werror to turn warnings into errors. I recommend always turning on at least -Werror=implicit, which ensures calling undeclared functions results in an error(!)
"Implicit declarations" is such a frustrating "feature" of C. Thankfully, in more recent clang builds this warning is enabled by default.
It's only UB if the original variable is declared with a const type. If you convert a modifiable T * into a const T *, then cast it back into a T *, then you can modify it without any issues.
This used to be my bible when doing full time C programming around 2000 (together with the standard docs) but I’m out of date with the latest standard updates (as is this) but it may still be of interest.
> I get an int* value which has 5 ints allocated at it.
Not crazy about this array explanation. Better wording would be: "I get a memory address that points to the first byte of a chunk of memory that is large enough to hold 5 ints and tagged as int"
> Essential compiler flags
Nitpick, but this is only true if you are on a gcc/clang platform.
> If you want to “return” memory from a function, you don’t have to use malloc/allocated storage; you can pass a pointer to a local data
It should be specified the memory must be passed by the caller for this to work. You can't create a var in a called function and return that (you can but you will get undefined behavior as that memory is up for grabs after the function returns).
> Integers are very cursed in C. Writing correct code takes some care.
Cursed how? No explanation.
Overall this article is not very good. I would add these to the lists:
General resources: Read the K&R book. Read the C spec. Be familiar with the computer architectures you are working on.
Good projects to learn from: Plan 9. Seriously. It's an entire OS that was designed to be as simple as possible by the same people who made Unix and the C.
Love the intro and overview — looking forward to more!
These weren't mentioned in the post but have been very helpful in my journey as a C beginner so far:
- Effective C by Robert C. Seacord. It covers a lot of the footguns and gotchas without assuming too much systems or comp-sci background knowledge. https://nostarch.com/Effective_C (Also, how can you not buy a book on C with Cthulhu on the cover written by a guy with _three_ “C”s in his name?)
- Computer Systems, A Programmer's Perspective by Randal E. Bryant and David R. O'Hallaron for a deeper dive into memory, caches, networking, concurrency and more using C, with plenty of practice problems: https://csapp.cs.cmu.edu/
typedef/enum/(_Generic)/etc should go (fix/cleanup function pointer type declaration). Only sized primitive types (u32/s32,u64/s64,f32/f64 or udw/sdw,uqw/sqw,fdw/fqw...). We would have only 1 loop statement "loop{}", no switch. I am still thinking about "anonymous" code blocks for linear-code variable sub-scoping (should be small compile-unit local function I guess). No integer promotion, no implicit cast (except for void* and maybe for literals like rust) with explicit compile-time/runtime casts (not with that horrible c++ syntax). Explicit compile-time/"scoped" runtime constants: currently it is only "scoped" runtime, with on some optimization passes to detect if the constant would be compile time. extern properly enforced for function plz (aka write proper headers with proper switches), and maybe "local" instead of "static" for compile-unit-only functions since all functions in a compile-unit are "static" anyway like global variables.
"Non-standard": ban attributes like binary format visibility (let the linker handle the fine details), packed (if your struct is packed and not compatible with the C struct, it should be byte defined, with proper pointer casts), etc.
Fix the preprocessor variable argument macro for good (now it is a mess because of gcc way and c++ ISO way, I guess we will stick to gcc way).
With the preprocessor and some rigorous coding, we should be able to approximate that with a "classic" C compiler, since we mostly remove stuff. Was told that many "classic" C compiler could output optional warnings about things like implicit casts and integer promotions.
In theory, this should help writting a naive "C-" compiler much more easily than a "classic" C compiler and foster real life alternatives.
I am sure stuff I said are plain broken as I did not write a C compiler.
I wonder how far rust is from this "C-" as it is expected to have a much less rich and complex, but more constraint, syntax than C.
There's a lot in "C", but I don't see anything in it that really isn't needed to serve its modern purpose.
In 2022 "C" is used as a portable assembly language. When you really need to control where and how memory is allocated, and represent data structures used directly by the hardware in a high-level language.
I sympathize a bit with the dislike of enum. We could also probably get away with replacing typedef's with either #define (for simple types) or one-field structs (for arrays and function pointers; the latter usually need a void* to go along with them anyway).
There are reasonable low-level optimizations you can do that switch is needed for. You can have cases that start or end around blocks in non-hierarchical ways. This makes it similar to a computed goto.
This is a very slipery slope (where gcc/clang[llvm] just run onto): adding tons of attributes/keywords/"syntax features" which gives the compiler some semantic hints in order to perform "better" optimizations. There is no end to it, this is a toxic spiral of infinite planned obsolescence (and now ISO is doing the same).
There is also this other thing: extreme generalization and code factorization, to a point, we would have no clue of what the code actually does without embracing the entirety of the code with its "model". It did reach a pathological level with c++.
And the last, but not the least: OS functions are being hardcoded in the syntax of the language.
If you push further all those points they kind of converge: compilers will have keywords specific for each performance critical syscall, significant library function, and significant data structure for abstraction (some abstraction can be too much very fast as I said before). There is a end game though: directly coding assembly.
This will print two different values if you have `int i = 1` and you call `foo(&i, &i)`. This is the classic C aliasing rule. The C standard guarantees that this works even under aggresive optimisation (in fact certain optimisations are prevented by this rule), whereas the analogous Fortrain wouldn't be guaranteed to work.
Definitely pick a simpler assembly than x86 assembly, and it's not so bad. I learned 68HC11 assembly which has been a boon for understanding what's happening underneath the hood.
The idea is not to write apps for a living in assembler, but to get an idea of what's going on under the hood. Just as C will help understanding what's going on under the hood of Javascript/Python etc.
I would add to this:
1. Visual Studio debugger is really, really good and you should use it to step through your program (or equivalent IDE based workflow).
2. Learn to compile your code with memory safeguards and use the tooling around them. Specific depends on the platform. On POSIX address sanitizer is good I hear. On Windows you can use the debug CRT and gflags to instrument your binary really well.
People should be using IDEs with modern debuggers anyways.
Being able to step through your code one line at time and instantly see the values of all your variables is going to be FAR more effective than adding a bunch of print statements, no matter what language you're using.
It always blows my mind to hear about how many engineers don't know how to use their IDE's debugger and don't know what a breakpoint is.
Perhaps there is not enough training available on these tools. Raw terminal GDB also is not very user friendly so if someone has experience from that it might be a disencouragement.
It really should be a standard part of any school curriculum, book, website, or any other media that people consume to learn programming. Debugging is such an integral skill to software engineering that it's beyond inexcusable that it's not taught.
Graphical debuggers aren't even hard to use. You can learn PyCharm's in an hour. Learn how to set a break point, examine local variables, learn the different step buttons, and view the function call stack and the local variables at each level of the stack.
Heck, maybe people wouldn't struggle with recursion so much if they were taught how to examine the call stack using the debugger, showing what happens when function A calls B which calls C, examine the local variables at each level of the stack, including an example where A and B both have a local variable called "x", and then note that function A calling itself is not a special case and adds another level to the stack with a new "x".
Without knowing how to use a debugger, learning to program feels like programming a black box. Sure, a bunch of "print" statements help, but nothing beats stepping through code line-by-line.
‘ You can’t extend structs or do anything really OO-like, but it’s a useful pattern to think with’
That’s not quite true. If you define 2 structs so that they start the same (eg: both with “int x; int y” in your example), pointers can be passed to functions with either struct type. You can use this to add fields (eg: int z) to structures, and extend a 2d vector into a 3d one…
With a bit of creative thought, and constructive use of pointer-to-functions, you can do quite a bit of OOP stuff in C.
In 2nd year of college, one of my friends and I figured this out on our own, which was a really rewarding experience. The code did look pretty hairy, though.
Details for anyone interested:
The CS course project was to write a game solver for a variety of games with perfect information (e.g: tic-tac-toe), and they highly suggested we use object-oriented design and recursion + backtracking. They also let us pick any language we wanted, and being computer engineers, between the two of us, we were most comfortable with C.
So we kind of started writing our project and implementing the first game, and when we got to the second, we were scratching our heads like, "Is it possible to just... take a pointer to a function?" "Yeah, it's just somewhere in memory, right?" And then everything fell into place, and we just had to define a struct with pointers to game state and "methods", and our TA was baffled that we did the project in C but we got a great grade.
Modulo any padding rules, I would be surprised if C didn't store them in the same offsets, the compatible-type specification[1] and equality operator[2] (also see footnote 109) would make implementation much harder if you tried to do it any other way. The way to work around that of course, and to make sure that no matter what, the parent/child objects are compatible to the parent-only level is to do something like:
// this structure defines a node of a linked list and
// it only holds the pointers to the next and the previous
// nodes in the linked list.
struct list_head {
struct list_head *next; // pointer to the node next to the current one
struct list_head *prev; // pointer to the node previous to the current one
};
// list_int holds a list_head and an integer data member
struct list_int {
struct list_head list; // common next and prev pointers
int value; // specific member as per implementation
};
// list_str holds a list_head and a char * data member
struct list_str {
struct list_head list; // common next and prev pointers
char \* str; // specific member as per implementation
};
Often the 'parent' structure would have an 'int objType', which can be switch'd on, to make sure that the receiving function knows what to do. I'm not really seeing any undefined behaviour here.
I'm pretty sure this technique is decades old, btw. I know at one point the linux kernel used it, not sure if it still does..
If you're happy with being C11-compliant, then remove any names for the 'parent' structure in the child structures, making them "anonymous structs" at which point they are [3] considered to be part of the same struct as the parent.
Even if the fields are stored in the same offsets, there are modern optimisations that rely on "strict aliasing" and that would cause issues (unless the workaround you described is used).
It may be strictly UB, but given that any operating system written in C relies on this behavior being well-defined (e.g. the container_of macro widely used in the Linux kernel) you're probably pretty safe.
Note that there is sort of an active war between the OS folks, who are probably the main users of pure C nowadays, and the UB nazis among the compiler folks who are mostly worried about efficiently optimizing complex template code in C++ and don't care whether their computers continue to run :-)
The linux kernel compiles with no strict aliasing, so kernel code is unaffected. In userspace this is still dodgy, strict aliasing does affect generated code, and correctness of code that relies on UB this way.
A pragmatic solution would be attributes that allowed declaring that certain pairs of types that are allowed to alias with each other. It would even be better if the C and C++ standards provided facilities for this, although it can be challenging to rigurously fit into the object model.
Strict aliasing allows you to convert between a pointer to a struct and a pointer to its first member, since two objects exist at that address, one with the type of the struct and one with the type of the first member.
> A pointer to a structure object, suitably converted, points to its initial member (or if that member is a bit-field, then to the unit in which it resides), and vice versa. There may be unnamed padding within a structure object, but not at its beginning.
Yes, that's the workaround described by @spacedcowboy and @leni53. I should have clarified that my comment on undefined behaviour was specifically for the case of two structs that start the same (e.g. "int x; int y;") as opposed to composition.
The defined way to do something like this is to have the smaller struct as the first member of the larger one. The first member is guaranteed to have the same address as the outer object.
The common initial sequence trick is guaranteed to work with unions in limited circumstances.
> The sizes of arrays are not known in any useful way to C. When I declare a variable of type int[5] in a function, I don’t get a value of type int[5]; I get an int* value which has 5 ints allocated at it. Since this is just a pointer, the programmer, not the language, has to manage copying the data behind it and keeping it valid.
This is not quite correct. Assuming arrays == pointers is usually true enough, but it isn't actually true. This[1] SO thread has some useful information in it, but the TLDR is that actual arrays are treated differently than pointers. You do get an object of type "int array" rather than "int pointer" when you do int[5].
The compiler does know the size of an array allocated like T a[n]. It does not, however, do any bounds checking for you.
400 comments
[ 8.7 ms ] story [ 1223 ms ] threadc89 or c99, not c98.
Plain static variables aren't thread-safe by default, true, but there's also _Thread_local
This is one of the main reasons why C is more portable than C++.
N1570, sec. 6.2.4, para. 4: An object whose identifier is declared with the storage-class specifier _Thread_local has thread storage duration. Its lifetime is the entire execution of the thread for which it is created, and its stored value is initialized when the thread is started. There is a distinct object per thread, and use of the declared name in an expression refers to the object associated with the thread evaluating the expression.
N1570, sec. 6.7.9, para. 10: If an object that has static or thread storage duration is not initialized explicitly, then [it is initialized to NULL or 0 as appropriate, including all-bits-zero padding in structs]
> "If you want to “return” memory from a function, you don’t have to use malloc/allocated storage; you can pass a pointer to a local data:
void getData(int *data) { data[0] = 1; data[1] = 4; data[2] = 9; }
void main() { int data[3]; getData(data); printf("%d\n", data[1]); } "
I prefer to use the variant you described though, because it feels more natural to associate the pointer with the type itself. As far as I know, the only pitfall is in the multiple declaration thing so I just don't use it.
IMO, it's also more readable in this case:
The second one more clearly shows that it returns a pointer-to-int.If you always set new variables in the same statement you declare them, then you don't use multiple declarations, which means there is no ambiguity putting the * by the type name.
So convention wins out for convention's sake. And that's the entire point of convention in the first place: to sidestep the ugly warts of a decades-old language design.
int*x,y; // x is pointer to int, y is int.
int x,*y; // x is int, y is pointer to int
And the reason I got it wrong on the test is it had been MANY years since I defined more than one variable in a statement (one variable defined per line is wordier but much cleaner), so if I ever knew this rule before, I had forgotten it over time.
I keep wanting to use slash-star comments, but I recall // is comment-to-end-of-line in C99 and later, something picked up from its earlier use in C++.
Oh yeah, C99 has become the de-facto "official" C language, regardless of more recent changes/improvements, as not all newer changes have made it into newer compilers, and most code written since 1999 seems to follow the C99 standard. I recall gcc and many other compilers have some option to specify which standard to use for compiling.
I think the syntax and the underpinning "declaration follows use" rule are what they got when they tried to generalize the traditional array declaration syntax with square brackets after the array name which they inherited directly from B, and ultimately all the way from Algol:
In B, though, arrays were not a type; when you wrote this: x, y, and z all have the same type (word); the [] is basically just alloca(). This all works because the type of element in any array is also the same (word), so you don't need to distinguish different arrays for the purposes of correctly implementing [].But in C, the compiler has to know the type of the array element, since it can vary. Which means that it has to be reflected in the type of the array, somehow. Which means that arrays are now a type, and thus [] is part of the type declaration.
And if you want to keep the old syntax for array declarations, then you get this situation where the type is separated by the array name in the middle. If you then try to formalize this somehow, the "declaration follows use" rule feels like the simplest way to explain it, and applying it to pointers as well makes sense from a consistency perspective.
a is int; &a is pointer to int; x is pointer to int; *x in again int.
---
As a sibling comment pointed out, this is ambiguous when using multiple declaration:
The above statement declares an "integer pointer" foo and an "integer" bar. It can be unambiguously rewritten as: But multiple declaration sucks anyway! It's widely accepted good practice to set (instantiate) your variables in the same statement that you declare them. Otherwise your program might start reading whatever data was lying around on the stack (the current value of bar) or worse: whatever random memory address it refers to (the current value of foo).> ..read a declaration like `int *x` as "`*x` is an int"..
*data would give you an int, &data would give you an int*.
The name of an array decays to a pointer to the first element in various contexts. You could do `&data[0]` but it means exactly the same thing and would read as over-complicated things to C programmers.
> Here are the absolute essential flags you may need.
I highly recommend including `-fsanitize=address,undefined` in there (docs: https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.h...).
(Edit: But probably not in release builds, as @rmind points out.)
> The closest thing to a convention I know of is that some people name types like my_type_t since many standard C types are like that
Beware that names beginning with "int"/"uint" and ending with "_t" are reserved in <stdint.h>.
[Edited; I originally missed the part about "beginning with int/uint", and wrote the following incorrectly comment: "That shouldn't be recommended, because names ending with "_t" are reserved. (As of C23 they are only "potentially reserved", which means they are only reserved if an implementation actually uses the name: https://en.cppreference.com/w/c/language/identifier. Previously, defining any typedef name ending with "_t" technically invokes undefined behaviour.)"]
The post never mentions undefined behaviour, which I think is a big omission (especially for programmers coming from languages with array index checking).
> void main() {
As @vmilner mentioned, this is non-standard (reference: https://en.cppreference.com/w/c/language/main_function). The correct declaration is either `int main(void)` or the argc+argv version.
(I must confess that I am guilty of using `int main()`, which is valid in C++ but technically not in C: https://stackoverflow.com/questions/29190986/is-int-main-wit...).
> You can cast T to const T, but not vice versa.
This is inaccurate. You can implicitly convert T* to const T*, but you need to use an explicit cast to convert from const T* to T*.
I find it difficult to imagine what that would even mean.
It is not required to do so, hence undefined behavior. You might get a wrong underlying type under that name.
I mean, no typedef names are defined in the global scope without including any headers right? Like I find it really weird that a type ending in _t would be UB if there is no such typedef name declared at all.
Or is this UB stuff merely a way for the ISO C committee to enforce this without having to define <something more complicated>?
The purpose of this particular naming rule is to allow adding new typedefs such as int128_t. The "undefined behaviour" part is for declaration of any reserved identifier (not specifically for this naming rule). I don't know why the standard uses "undefined behaviour" instead of the other classes (https://en.cppreference.com/w/cpp/language/ub); I suspect because it gives compilers the most flexibility.
>That shouldn't be recommended, because names ending with "_t" are reserved.
Also C spec naming new things:
>_Atomic _Bool
I'm glad to see the C folks have a sense of humor.
Also, apparently, "function names [...] beginning with 'is' or 'to' followed by a lowercase letter" are reserved if <ctype.h> and/or <wctype.h> are included. So apparently I can't have a function named "touch_page()" or "issue_command()" in my code. Just lovely.
> The goal of the future language and library reservations is to alert C programmers of the potential for future standards to use a given identifier as a keyword, macro, or entity with external linkage so that WG14 can add features with less fear of conflict with identifiers in user’s code. However, the mechanism by which this is accomplished is overly restrictive – it introduces unbounded runtime undefined behavior into programs using a future language/library reserved identifier despite there not being any actual conflict between the identifier chosen and the current release of the standard. ...
> Instead of making the future language/library identifiers be reserved identifiers, causing their use to be runtime unbounded undefined behavior per 7.1.3p1, we propose introducing the notion of a potentially reserved identifier to describe the future language and library identifiers (but not the other kind of reservations like __name or _Name). These potentially reserved identifiers would be an informative (rather than normative) mechanism for alerting users to the potential for the committee to use the identifiers in a future release of the standard. Once an identifier is standardized, the identifier stops being potentially reserved and becomes fully reserved (and its use would then be undefined behavior per the existing wording in C17 7.1.3p2). These potentially reserved identifiers could either be listed in Annex A/B (as appropriate), Annex J, or within a new informative annex. Additionally, it may be reasonable to add a recommended practice for implementations to provide a way for users to discover use of a potentially reserved identifier. By using an informative rather than normative restriction, the committee can continue to caution users as to future identifier usage by the standard without adding undue burden for developers targeting a specific version of the standard.
Am I completely misreading this or is this actually insane? Besides, there is already a huge swath of reserved identifiers in C, why do they feel the need to make an even larger chunk of names unavailable to the programmers?
In practical terms, what compilers will do is, if C2y adds a 'togoodness' function, they will add a warning to C89-C2x modes saying "this is now a library function in C2y," or maybe even have an extension to use the new thing in earlier modes. This is what they already do in large part; it's semantic wording to make this behavior allowable without resorting to the full unlimited power of UB.
The C23 change was mostly to downgrade some of the existing reserved identifiers from "reserved" to "potentially reserved". (It also added some new reserved and potentially reserved identifiers, but they seem reasonable to me.)
It also means that if an identifier becomes potentially reserved in C23 and reserved in C3X, then compiling a valid C11 program that uses it as C23 will give you a warning, which you can fix and then compile resulting valid C23 program as C3X without any problem; but compiling such a C11 program straight up as C3X will give you no warning and a program with UB.
Seriously, it boggles my mind. Just a) require diagnostics for invalid uses of reserved identifiers starting from C23, b) don't introduce new reserved identifiers, there is already a huge amount of them.
In addition, I recommend -fsanitize=integer. This adds checks for unsigned integer overflow which is well-defined but almost never what you want. It also checks for truncation and sign changes in implicit conversions which can be helpful to identify bugs. This doesn't work if you pepper your code base with explicit integer casts, though, which many have considered good practice in the past.
You can read a comparison with -fsanitize=function here:
https://clang.llvm.org/docs/ControlFlowIntegrity.html#fsanit...
There's also TypeSanitizer, which isn't officially released, but is really interesting and should be able to be applied via a patch from the branch:
https://www.youtube.com/watch?v=vAXJeN7k32Y
https://reviews.llvm.org/D32199
POSIX reserves "_t" suffix everywhere (not just for identifiers beginning with "int"/"uint" from <stdint.h>); references: https://www.gnu.org/software/libc/manual/html_node/Reserved-..., https://pubs.opengroup.org/onlinepubs/9699919799/functions/V....
So I actually stand by my original comment that the convention of using "_t" suffix shouldn't be recommended. (It's just that the reasoning is for conformance with POSIX rather than with ISO C.)
If available, it's much better to do the `snprintf()` way as I mentioned in a comment last week, i.e. replace `strcpy(dest, src)` with `snprintf(dst, sizeof dst, "%s", src)` and always remember that "%s" part. Never put src there, of course.
There's also `strlcpy()` on some systems, but it's not standard.
The strn* category was generally designed for fixed-size NUL-padded content (though not all of them because why be coherent?), the entire item is incorrect, and really makes the entire thing suspicious.
And they don’t actually “do exactly what you want”, see for instance N1967 (“field experience with annex k”) which is less than glowing.
Note that this only works if dst is a stack allocated(in the same function) array and not a char *
Could be an array inside a struct too for instance, that is quite common.
> Note that this only works if dst is a stack allocated array
Even this "ideal" solution is full of pitfalls. The state of memory safety is so sad in the world of C.
The problem is that `memcpy()` doesn't know about (of course) string terminators, so you have to do a separate call to `strlen()` to figure out the length, thus visiting every character twice which of course makes no sense at all (spoken like a C programmer I guess ... since I am one).
If you already know the length due to other means, then of course it's fine to use `memcpy()` as long as you remember to include the terminator. :)
But the fifth reason may be that depending on snprintf as your "custom-validator-and-encoder-plus-null-terminator" may introduce subtle bugs in your program if you don't know exactly what snprintf is doing under the hood and what its limitations are. By using memcpy and a custom validator, you can be more explicit about how data is handled in your program and avoid uncertainty.
(by "validate" I mean handle the data as your program expects. this could be differentiating between ASCII/UTF-8/UTF-16/UTF-32, adding/preserving/removing a byte-order mark, eliminating non-printable characters, length requirements, custom terminators, or some other requirement of whatever is going to be using the new copy of the data)
"Implicit declarations" is such a frustrating "feature" of C. Thankfully, in more recent clang builds this warning is enabled by default.
Humm, actually, you can. But guess what ? Modification of the underlying data is an UB !
const_cast is there for buggy (or legacy) libraries where const was not specified explicitly, but is implicit in the behavior.
not sure if fuzz testing can help here.
I wonder how many implicit-const-to-non-cost API are in ANSI C and Posix C
https://c-faq.com/
Not crazy about this array explanation. Better wording would be: "I get a memory address that points to the first byte of a chunk of memory that is large enough to hold 5 ints and tagged as int"
> Essential compiler flags
Nitpick, but this is only true if you are on a gcc/clang platform.
> If you want to “return” memory from a function, you don’t have to use malloc/allocated storage; you can pass a pointer to a local data
It should be specified the memory must be passed by the caller for this to work. You can't create a var in a called function and return that (you can but you will get undefined behavior as that memory is up for grabs after the function returns).
> Integers are very cursed in C. Writing correct code takes some care.
Cursed how? No explanation.
Overall this article is not very good. I would add these to the lists:
General resources: Read the K&R book. Read the C spec. Be familiar with the computer architectures you are working on.
Good projects to learn from: Plan 9. Seriously. It's an entire OS that was designed to be as simple as possible by the same people who made Unix and the C.
But keep in mind that C is not a portable assembly!
These weren't mentioned in the post but have been very helpful in my journey as a C beginner so far:
- Effective C by Robert C. Seacord. It covers a lot of the footguns and gotchas without assuming too much systems or comp-sci background knowledge. https://nostarch.com/Effective_C (Also, how can you not buy a book on C with Cthulhu on the cover written by a guy with _three_ “C”s in his name?)
- Tiny C Projects by Dan Gookin, for a “learn by doing” approach. https://www.manning.com/books/tiny-c-projects
- Exercism's C language track: https://exercism.org/tracks/c
- Computer Systems, A Programmer's Perspective by Randal E. Bryant and David R. O'Hallaron for a deeper dive into memory, caches, networking, concurrency and more using C, with plenty of practice problems: https://csapp.cs.cmu.edu/
typedef/enum/(_Generic)/etc should go (fix/cleanup function pointer type declaration). Only sized primitive types (u32/s32,u64/s64,f32/f64 or udw/sdw,uqw/sqw,fdw/fqw...). We would have only 1 loop statement "loop{}", no switch. I am still thinking about "anonymous" code blocks for linear-code variable sub-scoping (should be small compile-unit local function I guess). No integer promotion, no implicit cast (except for void* and maybe for literals like rust) with explicit compile-time/runtime casts (not with that horrible c++ syntax). Explicit compile-time/"scoped" runtime constants: currently it is only "scoped" runtime, with on some optimization passes to detect if the constant would be compile time. extern properly enforced for function plz (aka write proper headers with proper switches), and maybe "local" instead of "static" for compile-unit-only functions since all functions in a compile-unit are "static" anyway like global variables.
"Non-standard": ban attributes like binary format visibility (let the linker handle the fine details), packed (if your struct is packed and not compatible with the C struct, it should be byte defined, with proper pointer casts), etc.
Fix the preprocessor variable argument macro for good (now it is a mess because of gcc way and c++ ISO way, I guess we will stick to gcc way).
With the preprocessor and some rigorous coding, we should be able to approximate that with a "classic" C compiler, since we mostly remove stuff. Was told that many "classic" C compiler could output optional warnings about things like implicit casts and integer promotions.
In theory, this should help writting a naive "C-" compiler much more easily than a "classic" C compiler and foster real life alternatives.
I am sure stuff I said are plain broken as I did not write a C compiler.
I wonder how far rust is from this "C-" as it is expected to have a much less rich and complex, but more constraint, syntax than C.
In 2022 "C" is used as a portable assembly language. When you really need to control where and how memory is allocated, and represent data structures used directly by the hardware in a high-level language.
No! Only poor programmers use C while reasoning assembly, and then they complain about undefined behaviour.
There are reasonable low-level optimizations you can do that switch is needed for. You can have cases that start or end around blocks in non-hierarchical ways. This makes it similar to a computed goto.
There is also this other thing: extreme generalization and code factorization, to a point, we would have no clue of what the code actually does without embracing the entirety of the code with its "model". It did reach a pathological level with c++.
And the last, but not the least: OS functions are being hardcoded in the syntax of the language.
If you push further all those points they kind of converge: compilers will have keywords specific for each performance critical syscall, significant library function, and significant data structure for abstraction (some abstraction can be too much very fast as I said before). There is a end game though: directly coding assembly.
I would add "... cannot be modified through that pointer". (Yes, in fairness, they did say "roughly".) For example consider the following:
This will print two different values if you have `int i = 1` and you call `foo(&i, &i)`. This is the classic C aliasing rule. The C standard guarantees that this works even under aggresive optimisation (in fact certain optimisations are prevented by this rule), whereas the analogous Fortrain wouldn't be guaranteed to work.The most common example is when y is float* and someone tries to access its bitwise representation via an int*.
(Please correct me if I'm wrong)
https://gist.github.com/shafik/848ae25ee209f698763cffee272a5...
I do mean real z80s not emulators.
0. https://www.retrobrewcomputers.org/doku.php?id=boards:sbc:st...
A lot of things can be done without using malloc and free.
Use these functions to manage memory in large clusters. Let the compiler deal with the rest.
Probably the best open source and cross platform C/C++ IDE around.
Being able to step through your code one line at time and instantly see the values of all your variables is going to be FAR more effective than adding a bunch of print statements, no matter what language you're using.
It always blows my mind to hear about how many engineers don't know how to use their IDE's debugger and don't know what a breakpoint is.
Graphical debuggers aren't even hard to use. You can learn PyCharm's in an hour. Learn how to set a break point, examine local variables, learn the different step buttons, and view the function call stack and the local variables at each level of the stack.
Heck, maybe people wouldn't struggle with recursion so much if they were taught how to examine the call stack using the debugger, showing what happens when function A calls B which calls C, examine the local variables at each level of the stack, including an example where A and B both have a local variable called "x", and then note that function A calling itself is not a special case and adds another level to the stack with a new "x".
Without knowing how to use a debugger, learning to program feels like programming a black box. Sure, a bunch of "print" statements help, but nothing beats stepping through code line-by-line.
That’s not quite true. If you define 2 structs so that they start the same (eg: both with “int x; int y” in your example), pointers can be passed to functions with either struct type. You can use this to add fields (eg: int z) to structures, and extend a 2d vector into a 3d one…
With a bit of creative thought, and constructive use of pointer-to-functions, you can do quite a bit of OOP stuff in C.
Details for anyone interested: The CS course project was to write a game solver for a variety of games with perfect information (e.g: tic-tac-toe), and they highly suggested we use object-oriented design and recursion + backtracking. They also let us pick any language we wanted, and being computer engineers, between the two of us, we were most comfortable with C. So we kind of started writing our project and implementing the first game, and when we got to the second, we were scratching our heads like, "Is it possible to just... take a pointer to a function?" "Yeah, it's just somewhere in memory, right?" And then everything fell into place, and we just had to define a struct with pointers to game state and "methods", and our TA was baffled that we did the project in C but we got a great grade.
Plus you cant cast and dereference one type's pointer to the other. That would be UB
I'm pretty sure this technique is decades old, btw. I know at one point the linux kernel used it, not sure if it still does..
If you're happy with being C11-compliant, then remove any names for the 'parent' structure in the child structures, making them "anonymous structs" at which point they are [3] considered to be part of the same struct as the parent.
[1]: http://port70.net/~nsz/c/c11/n1570.html#6.2.7p1
[2]: http://port70.net/~nsz/c/c11/n1570.html#6.5.9p6
[3]: http://port70.net/~nsz/c/c11/n1570.html#6.7.2.1p13
Note that there is sort of an active war between the OS folks, who are probably the main users of pure C nowadays, and the UB nazis among the compiler folks who are mostly worried about efficiently optimizing complex template code in C++ and don't care whether their computers continue to run :-)
A pragmatic solution would be attributes that allowed declaring that certain pairs of types that are allowed to alias with each other. It would even be better if the C and C++ standards provided facilities for this, although it can be challenging to rigurously fit into the object model.
> A pointer to a structure object, suitably converted, points to its initial member (or if that member is a bit-field, then to the unit in which it resides), and vice versa. There may be unnamed padding within a structure object, but not at its beginning.
The defined way to do something like this is to have the smaller struct as the first member of the larger one. The first member is guaranteed to have the same address as the outer object.
The common initial sequence trick is guaranteed to work with unions in limited circumstances.
* https://dystroy.org/blog/how-not-to-learn-rust/
* https://federicoterzi.com/blog/12-rust-tips-and-tricks-you-m...
This is not quite correct. Assuming arrays == pointers is usually true enough, but it isn't actually true. This[1] SO thread has some useful information in it, but the TLDR is that actual arrays are treated differently than pointers. You do get an object of type "int array" rather than "int pointer" when you do int[5].
The compiler does know the size of an array allocated like T a[n]. It does not, however, do any bounds checking for you.
[1] https://stackoverflow.com/questions/4607128/in-c-are-arrays-...
> I eschew embedded capital letters in names; to my prose-oriented eyes, they are too awkward to read comfortably. They jangle like bad typography.