zoomablemind 16 minutes ago

"...C is my favorite language and I love the freedom and exploration it allows me. I also love that it is so close to Assembly and I love writing assembly for much of the same reasons!"

I wonder what is author's view about user's reasons to choose a C API?

What I mean is users may want exactly the same freedom and immediacy of C that the author embraces. However, the very approach to encapsulation by hiding the layout of the memory, the use of accessor functions limits the user's freedom and robs them of performance too.

In my view, the choice of using C in projects comes with certain responsibilities and expectations from the user. Thus higher degree of trust to the API user is due.

writebetterc 2 hours ago

void* is basically used for ad-hoc polymorphism in C, and it is a vital part of C programming.

    void new_thread(void (*run)(void*), void* context);
^- This let's us pass arbitrary starting data to a new thread.

I don't know whether this counts as "very few use cases".

The Memory Ownership advice is maybe good, but why are you allocating in the copy routine if the caller is responsible for freeing it, anyway? This dependency on the global allocator creates an unnecessarily inflexible program design. I also don't get how the caller is supposed to know how to free the memory. What if the data structure is more complex, such as a binary tree?

It's preferable to have the caller allocate the memory.

    void insert(BinTree *tree, int key, BinTreeNode *node);
^- this is preferable to the variant where it takes the value as the third parameter. Of course, an intrusive variant is probably the best.

If you need to allocate for your own needs, then allow the user to pass in an allocator pointer (I guessed on function pointer syntax):

    struct allocator { void* (*new)(size_t size, size_t alignment); void (*free)(void* p, size_t size); void* context; }.*
  • warmwaffles 32 minutes ago

    Curious about the allocator, why pass a size when freeing?

    • writebetterc 9 minutes ago

      You know the size of your objects, so it's duplicate knowledge for the allocator to have to deal with, if free doesn't also pass in the size.

fpotier an hour ago

void employee_set_age(struct Employee* employee, int newAge) { // Cast away the const and set it's value, the compiler should optimize this for you (int)&employee->age = newAge; }

I believe that "Casting away the const" is UB [1]

[1]: https://en.cppreference.com/w/c/language/const.html

  • spacechild1 an hour ago

    It's only UB if the pointed to object is actually const (in which case it might live in read-only memory).

unwind 2 hours ago

Quite interesting, and felt fairly "modern" (which for C programming advice sometimes only means it's post-2000 or so). A few comments:

----

This:

    struct Vec3* v = malloc(sizeof(struct Vec3));
is better written as:

    struct Vec3 * const v = malloc(sizeof *v);
The `const` is perhaps over-doing it, but it makes it clear that "for the rest of this scope, the value of this pointer won't change" which I think is good for readability. The main point is "locking" the size to the size of the type being pointed at, rather than "freely" using `sizeof` the type name. If the type name later changes, or `Vec4` is added and code is copy-pasted, this lessens the risk of allocating the wrong amount and is less complicated.

----

This is maybe language-lawyering, but you can't write a function named `strclone()` unless you are a C standard library implementor. All functions whose names begin with "str" followed by a lower-case letter are reserved [1].

----

This `for` loop header (from the "Use utf8 strings" section:

    for (size_t i = 0; *str != 0; ++len)
is just atrocious. If you're not going to use `i`, you don't need a `for` loop to introduce it. Either delete (`for(; ...` is valid) or use a `while` instead.

----

In the "Zero Your Structs" section, it sounds as if the author recommends setting the bits of structures to all zero in order to make sure any pointer members are `NULL`. This is dangerous, since C does not guarantee that `NULL` is equivalent to all-bits-zero. I'm sure it's moot on modern platforms where implementations have chosen to represent `NULL` as all-bits-zero, but that should at least be made clear.

[1]: https://www.gnu.org/software/libc/manual/html_node/Reserved-...

jjgreen 3 hours ago

Outstanding, why hadn't I come across this before?

Jean-Papoulos 3 hours ago

>What this means is that you can explain all the intent of your code through the header file and the developer who uses your lib/code never has to look at the actual implementations of the code.

I hate this. If my intellisense isn't providing sufficient info (generated from doc comments), then I need to go look at the implementation. This just adds burden.

Headers are unequivocally a bad design choice, and this is why most of every language past the nineties got rid of them.

  • alextingle an hour ago

    Separating interface from implementation of one of the core practices for making large code bases tractable.

    • valleyer 39 minutes ago

      Of course, but that's doable without making programmers maintain headers, and some modern languages do that.

  • GhosT078 2 hours ago

    Look to Ada for “headers” (i.e. specs) done right.

  • antonvs an hour ago

    C's text preprocessor headers were a pragmatic design choice in the 1970s. It's just that the language stuck around longer than it deserved to.