Live data from Hacker News

Modular C: Reusable and Maintainable Code Using the C Language (1994) [pdf]

metamodular.com

21–30 of 35 posts

Re: Modular C: Reusable and Maintainable Code Using the C Language (1994) [pdf]

#21
post #3

"System programming differs from application programming in that system programming emphasizes performance over reusable and maintainable code" Maybe for sufficiently small systems. But on a large scale product (reusable and maintainable code == performance). Am I missing something. Seems like something one or our EEs would say to me to excuse their poor code quality

no, that's not true.. in C, extracting struct elements into variables, holding temporary values for longer, explicitly adding intermediate steps in a dense reference of references, would be examples of 'optional' contents that help readability and lessen potential ambiguity to a human coder. Each time data moves, clock cycles are used, at least.

For decades, compilers have looked for this sort of code and condensed it as an optimization, so the way the code is written is not always the way it executes at runtime, but the longer, verbose steps might add cycles, and therefore would be avoided when coding for 'systems performance'.

Re: Modular C: Reusable and Maintainable Code Using the C Language (1994) [pdf]

#22

Earlier quoted context omitted.

Not a huge fan of those kind of macros myself, because they still leave too much of the implementation exposed. The other alternative is pointer-arithmetic implementations, which do hide the implementation better but often come with limitations (e.g. list members have to be first and you can't be on two lists at once). But either way, that's C for you. The language has its limitations, and there's only so much you ca…

For double-linked list, a better choice is to use intrusive data structures. Cleaner and more flexible than both macros and void pointers. However, only double-linked list and binary search trees, to a lesser extend, can be implemented with intrusive data structures.

I agree that intrusive data structures are often a better choice and do not have the issues GP mentions (by using something like a container_of() macro, so the data structure member does not need to be the first member, and the object can be in multiple data structures at once). But of course many other "pointer-based" data structures such as singly-linked lists, separate chaining hash tables, etc can also be implemented with them.

Re: Modular C: Reusable and Maintainable Code Using the C Language (1994) [pdf]

#23
post #3

"System programming differs from application programming in that system programming emphasizes performance over reusable and maintainable code" Maybe for sufficiently small systems. But on a large scale product (reusable and maintainable code == performance). Am I missing something. Seems like something one or our EEs would say to me to excuse their poor code quality

no, that's not true.. in C, extracting struct elements into variables, holding temporary values for longer, explicitly adding intermediate steps in a dense reference of references, would be examples of 'optional' contents that help readability and lessen potential ambiguity to a human coder. Each time data moves, clock cycles are used, at least. For decades, compilers have looked for this sort of code and condensed i…

Optimizing compilers are good at optimizing obvious code, so this usually isn’t a trade off. Many of the clever hacks you can find in books from the 80s are irrelevant now because the compiler can figure out what your naive code is doing and emit something optimized.

Modern processors don’t even operate sequentially and will execute multiple lines in parallel when there are no data dependencies.

For most code, the performance killer is when you try to be too clever or have too many pointer indirections. The compiler has a harder time with this, and is also precluded from applying other optimizations like auto vectorization, because it cannot figure out if it would change the meaning of your code.

For true performance, you need to enter the land of intrinsics, manual vectorization, and cache-aware algorithms. Domains which very few engineers are qualified to work on.

So just keep it simple and trust the compiler. This also applies to algorithms, complicated ones with lots of branching will often perform far far worse in practice than the naive one, you can’t trust the big-O alone. So profile when in doubt.

Re: Modular C: Reusable and Maintainable Code Using the C Language (1994) [pdf]

#24
post #5

This is old-fashioned (uses C89 instead of C99) and some of its "idioms" are questionable, like this one: list *p; list l = (list) malloc(sizeof(struct list)); l -> val = val; for(p = &the_list; *p && (*p) -> val next)) ; l -> next = *p; *p = l; Here p is a doubly indirect pointer and I would personally think this is an unwarranted cleverness. Furthermore this is the year 2019. Why would you still manually traverse a…

Holy cow. Defining list to be an alias for a struct list pointer is itself bonkers. Section 4.9: "Client code may consider that the pointer IS the object." Well now.

Re: Modular C: Reusable and Maintainable Code Using the C Language (1994) [pdf]

#26
post #17

At this point it's better just to convert the code into the proper modern programming language without all C drawbacks - Rust. Amazing c2rust[1] tool offers automated conversion[2] and refactoring[3] capabilities. It even allows you to script[4] the refactoring with Lua. All while preserving ability to run and (probably) pass the tests. [1] https://github.com/immunant/c2rust [2] https://c2rust.com/manual/quickstart.h…

Here comes the guy everyone was expecting.

Re: Modular C: Reusable and Maintainable Code Using the C Language (1994) [pdf]

#27
post #19
post #17

At this point it's better just to convert the code into the proper modern programming language without all C drawbacks - Rust. Amazing c2rust[1] tool offers automated conversion[2] and refactoring[3] capabilities. It even allows you to script[4] the refactoring with Lua. All while preserving ability to run and (probably) pass the tests. [1] https://github.com/immunant/c2rust [2] https://c2rust.com/manual/quickstart.h…

> the proper modern programming language I understand wanting to advocate for your favorite programming language but this kind of elitist attitude doesn't do the cause any favors, it's more likely to turn people away than anything. Rust is not "the proper programming language". It's just one of many excellent choices.

You're absolutely right. These people have seriously marred my view of the language.

Re: Modular C: Reusable and Maintainable Code Using the C Language (1994) [pdf]

#28
post #5

This is old-fashioned (uses C89 instead of C99) and some of its "idioms" are questionable, like this one: list *p; list l = (list) malloc(sizeof(struct list)); l -> val = val; for(p = &the_list; *p && (*p) -> val next)) ; l -> next = *p; *p = l; Here p is a doubly indirect pointer and I would personally think this is an unwarranted cleverness. Furthermore this is the year 2019. Why would you still manually traverse a…

It's sort of funny that linus torvalds actually talked about why that sort of iteration is better: https://youtu.be/o8NPllzkFhE?t=861 I think he puts it pretty well (although he them also goes on to say dumb little snippets like that really don't matter in the big picture). Essentially, by making a doubly indirect pointer you get to treat the first element exactly the same way you are going to treat every other eleme…

Exactly. And using such indirection often allows you to write a single search function and then use it to simply find and return the element, or find the spot and do something else: insert a new one, or replace or delete what you've found.

Re: Modular C: Reusable and Maintainable Code Using the C Language (1994) [pdf]

#29
post #5

This is old-fashioned (uses C89 instead of C99) and some of its "idioms" are questionable, like this one: list *p; list l = (list) malloc(sizeof(struct list)); l -> val = val; for(p = &the_list; *p && (*p) -> val next)) ; l -> next = *p; *p = l; Here p is a doubly indirect pointer and I would personally think this is an unwarranted cleverness. Furthermore this is the year 2019. Why would you still manually traverse a…

A a pointer to a pointer is a perfectly valid C idiom. I'd say typedef'ing a pointer would raise more eyebrows :)

E.g. I have a singly-linked list of

    struct x { int y; struct x* next };
I want to locate an element here and then maybe remove it (1), maybe insert a new one before (2) or after (3), maybe replace (4), or maybe just return what I've found (5). How do I write this base locator function? If I do it naively and return just a pointer to struct x I can only do (3) and (5); but if I return a pointer to a pointer, I can do all five.

A doubly-linked list is simpler in this regard because the element has pointers to both neighbors and thus a mere pointer to struct x would suffice, but it's also one pointer more expensive. By the way, will your C++ code work with singly-linked lists?

Also, look: your C++ example calls four methods. The C code calls only one (malloc), all the rest is core language. It's 4:1 ratio for extra complexity.

Re: Modular C: Reusable and Maintainable Code Using the C Language (1994) [pdf]

#30

Earlier quoted context omitted.

no, that's not true.. in C, extracting struct elements into variables, holding temporary values for longer, explicitly adding intermediate steps in a dense reference of references, would be examples of 'optional' contents that help readability and lessen potential ambiguity to a human coder. Each time data moves, clock cycles are used, at least. For decades, compilers have looked for this sort of code and condensed i…

Optimizing compilers are good at optimizing obvious code, so this usually isn’t a trade off. Many of the clever hacks you can find in books from the 80s are irrelevant now because the compiler can figure out what your naive code is doing and emit something optimized. Modern processors don’t even operate sequentially and will execute multiple lines in parallel when there are no data dependencies. For most code, the pe…

> So just keep it simple and trust the compiler. This also applies to algorithms, complicated ones with lots of branching will often perform far far worse in practice than the naive one, you can’t trust the big-O alone. So profile when in doubt.

I've found that optimizing for cache is usually the biggest gain when trying to be clever with algorithms. Eliminating branches and being naive in the algorithm itself is usually a better idea than trying to be clever with special cases.

In my experience.

Post reply on HN