Live data from Hacker News

Everything I wish I knew when learning C

tmewett.com

121–130 of 401 posts

Re: Everything I wish I knew when learning C

#121

I like it, but the array details are a little bit off. An actual array does have a known size, that's why when given a real array `sizeof` can give the size of the array itself rather than the size of a pointer. There's no particular reason why C doesn't allow you to assign one array to another of the same length, it's largely just an arbitrary restriction. As you noted, it already has to be able to do this when assi…

Another weird property about C arrays is that &arr == arr. The reference of an array is the pointer to the first element, which is what `arr` itself decays to. If arr was a pointer, &arr != arr.

Re: Everything I wish I knew when learning C

#122

I find this article very strange, perhaps because I started using 'c' so long ago. To the bullet points: (1) In general, 'c' is always 'c' at the command line, regardless of the platform. (2) yes, there are options and build tools, but cc my_program.c -o my_program works fine. I have a very hard time figuring out how to compile/run java. (3) hard to see how this has anything to do with 'C', vs any other compiled lang…

Now I need to make a C dialect with better string handling built-in and call it 'C'

Re: Everything I wish I knew when learning C

#123

‘ 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 const…

I would be surprised if C guarantees those x and y to have same offsets Plus you cant cast and dereference one type's pointer to the other. That would be UB

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.

[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

Re: Everything I wish I knew when learning C

#124

‘ 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 const…

This technique is used in Berkley sockets: the `sockaddr` type (https://pubs.opengroup.org/onlinepubs/009695399/basedefs/sys...) is the "base" type for other types such as `sockaddr_ip` (for IPv4). But it's undefined behaviour according to ISO C due to the "strict aliasing" rule: https://en.cppreference.com/w/c/language/object#Strict_alias...

Re: Everything I wish I knew when learning C

#125
post #74

> Everything I wish I knew when learning C By far my biggest regret is that the learning materials I was exposed to (web pages, textbooks, lectures, professors, etc.) did not mention or emphasize how insidious undefined behavior is. Two of the worst C and C++ debugging experiences I had followed this template: Some coworker asked me why their function was crashing, I edit their function and it sometimes crashes or do…

> how insidious undefined behavior is.

Indeed. UB in C doesn't mean "and then the program goes off the rails", it means that the entire program execution was meaningless, and no part of the toolchain is obligated to give any guarantees whatsoever if the program is ever executed, from the very first instruction. A UB-having program could time-travel back to the start of the universe, delete it, and replace the entire universe with a version that did not give rise to humans and thus did not give rise to computers or C, and thus never exist.

It's so insidiously defined because compilers optimize based on UB; they assume it never happens and will make transformations to the program whose effects could manifest before the UB-having code executes. That effectively makes UB impossible to debug. It's monumentally rude to us poor programmers who have bugs in our programs.

Re: Everything I wish I knew when learning C

#126
post #40

C syntax is already too rich and complex. 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 integ…

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.

Re: Everything I wish I knew when learning C

#127
The most important thing I had to learn when I started on hardcore ANSI C projects (large embedded business critical application) was to really learn to build abstractions.

C has few tools to help you with this and so it is super important to get the most of what is available.

Another lesson is that idioms are very useful in C and cut a ton of time. For most or all repeatable tasks there should be conventions for how to implement it and how to use it.

These are useful in any programming language and environment but I think are especially useful in C where you are on your own when structuring your application.

Re: Everything I wish I knew when learning C

#128

" ... is often called the stack." " integers are cursed" These statements and a few others made me uncomfortable. They imply, to me, that the author has too little knowledge of computer internals to be programming in C. C does a wonderful job of looking like a high level language but it was designed to do low level stuff. There is an implicit assumption, to my mind, that the user has a deeper understanding of what is…

I think the "automatic storage" terminology is technically correct. C can be used in places where there is no actual "stack" and these still need a mechanism for local variables, so they specify a different kind of automatic storage. Everyone uses a stack now, though, even very exotic processors.

In the standard it's "automatic storage duration" which is one of the three (or four in C11) classes of storage duration.

Re: Everything I wish I knew when learning C

#129
post #74

> Everything I wish I knew when learning C By far my biggest regret is that the learning materials I was exposed to (web pages, textbooks, lectures, professors, etc.) did not mention or emphasize how insidious undefined behavior is. Two of the worst C and C++ debugging experiences I had followed this template: Some coworker asked me why their function was crashing, I edit their function and it sometimes crashes or do…

I haven’t used C or C++ for anything, but in writing a Game Boy emulator I ran into exactly that kind of memory corruption pain. An opcode I implemented wrong causes memory to corrupt, which goes unnoticed for millions of cycles or sometimes forever depending on the game. Good luck debugging that!

My lesson was: here’s a really really good case for careful unit testing.

Re: Everything I wish I knew when learning C

#130
post #111
post #98

When I first learned C - which also was my first contact with programming at all - I did not understand how pointers work, and the book I was using was not helpful at all in this department. I only "got" pointers like three or four years later, fortunately programming was still a hobby at that point. Funnily when I felt confident enough to tell other people about this, several immediate started laughing and told me w…

Having basic experience in any assembly language makes pointers far more clear. "Addressing modes," where a register and some constant are used to calculate the source or target of a memory operation, make the equivalence of a[b]==*(a+b) much more obvious. I also wonder about the author's claims that a char is almost always 8 bits. The first SMP machine that ran Research UNIX was a 36-bit UNIVAC. I think it was ASCII…

> Having basic experience in any assembly language makes pointers far more clear.

That's a key point. I came to C after several years of programming in assembly and a pointer was an obvious thing. But I can see that for someone coming to C from higher level languages it might be an odd thing.

Post reply on HN