8 ms·
Writing C for Curl
- johnisgood 2y ago> Code should be easy to read. It should be clear. No hiding code under clever constructs, fancy macros or overloading. I highly agree with this. I do not always want highly abstracted code, and some programming languages aiming to replace C are much more difficult to read, that said, Rust is supposed to replace C++, not C, right? Thank you for the article!
- MrMcCall 2y agoThe reality is that we spend FAR more time reading code than writing it. That is why readability is far more important than clever, line saving constructs. The key to further minimizing the mental load of reacquainting yourself with older existing code is to decide on a set of code patterns and then be fastidious in using them. And then, if you want to want to be able to easily write a parser for your own code (without every detail in the spec), it's even more important. And now that I have read TFA, I see he wrote: > We have tooling that verify basic code style compliance. His experience and dilligence has led him to the mountaintop, that being we must make ourselves mere cogs in a larger machine, self-limiting ourselves for the greater good of our future workload and production quality.
- johnisgood 2y ago> That is why readability is far more important than clever, line saving constructs. Yes, I agree, that is why I am put off by some supposed C replacements that are trying to be clever with their abstractions or constructs.
- MrMcCall 2y agoSimplicity is essential to achieving managable complexity over time.
- kryptiskt 2y agoAbstraction is necessary to handle scale. If you have painstakingly arrived at a working solution for a complex problem like say locking, you want to be able to package it up and use it throughout your codebase. C lacks mechanisms to do this apart from using its incredibly brittle macro facility.
- johnisgood 2y agoAda has built-in constructs for concurrency, with contracts, and there is formal verification in a subset of Ada named SPARK, so Ada / SPARK is pretty good.
- MrMcCall 2y ago> C lacks mechanisms to do this apart from using its incredibly brittle macro facility. We programmers are the ultimate abstraction mechanism, and refining our techniques in pattern design and implementation in a codebase is our highest form of art. The list of patterns in the Gang-of-Four's "Design Patterns" are not as interesting as its first 50 pages, which are seminal. From the organization of files in a project, to organization of projects, to class structure and use, to function design, to debug output, to variable naming as per scope, to commandline argument specification, to parsing, it's nothing but patterns upon patterns. You're either doing patterns or you're doing one-offs, and one-offs are more brittle than C macros, are hard to comprehend later, and when you fix a bug in one, you've only fixed one bug, not an entire class of bugs. Abstraction is the essense of programming, and abstraction is just pattern design and implementation in a codebase, the design of a functional block and how it's consumed over time. The layering of abstractions is the most fundamental perspective on a codebase. They not only handle scale, they make or break correctness, ease of malleability, bug triage, performance, and comprehendability -- I'm sure I could find more. The design of the layering of abstractions is the everything of a codebase. The success of C's ability to let programmers create layers of abstractions is why C is the foundational language of the OS I'm using, as well as the browser I'm typing this message in. I'm guessing you are, too, and, while I could be wrong, it's not likely. And not a segfault in sight. The scale of Unix is unmatched.
- pjc50 2y agoCould you give an example of "clever" (bad) vs "simple" (good)? In my experience C has a lot of simple grammar, a commonly-held simple (wrong) execution model, and a lot more complexity lurking underneath where it can't be so easily seen. (One of my formative learning books was https://en.wikipedia.org/wiki/C_Traps_and_Pitfalls https://en.wikipedia.org/wiki/C_Traps_and_Pitfalls , valid in the 90s and mostly still valid today)
- baumschubser 2y ago> The reality is that we spend FAR more time reading code than writing it. That is why readability is far more important than clever, line saving constructs. In JS sometimes chain two or three inline-arrow-functions specifically for readability. When you read code, you often search for the needle of "the real thing" in a haystack of data formatting, API response prepping, localization, exception handling etc. Sometimes those shorthand constructs help me to skip the not-so-relevant parts instead of mentally climbing down and up every sort and rename function. That being said, I would not want this sentiment formalized in code guidelines :) And JS is not C except both have curly braces.
- MrMcCall 2y ago> That being said, I would not want this sentiment formalized in code guidelines :) Surely. I'm all for code formatting standards as long as they're MY code formatting standards :-) Ideally, I'd like the IDE to format the code to the user/programmer's style on open, but save the series of tokens to the code database in a formatting-agnostic fashion. Then we could each have our own style but still have a consistent codebase. And, I should add that my formatting conventions have gotten more extreme and persnickety over the years, and I now put spaces on both sides of my commas, because they're a separate token and are not a part of the expression on either side of it. I did this purely for readability, but I have NEVER seen anyone do that in all my decades on the internet reading code and working on large codebases. But I really like how spacing it out separates the expression information from the structural information. It also helps me deal with my jettisoning code color formatting, as, as useful as I've found it in the past, I don't want to deal with having to import/set all that environmental stuff in new environments. So, I just use bland vi with no intelligence, pushing those UI bells and whistles out of it into my code formatting. And, I fully endorse whatever it takes for you to deal with JS, as I have loathed it since it appeared on the scene, but that's just me being an old-school C guy.
- Zambyte 2y agoI have been playing around a lot with Zig lately, and though it's still in beta, it really feels like it has the best chance at being a true C successor. While Rust feels like they started with C++ and worked on making it harder to write incorrectly, Zig feels like they started with C and worked on making it easier to write correctly. They also have a few pillars that they call the "Zen" of Zig[0], of which three out of the first five are directly related to readability. [0] https://ziglang.org/documentation/0.14.0/#Zen https://ziglang.org/documentation/0.14.0/#Zen
- voidUpdate 2y agoI really want to start trying to learn zig, but right now I feel like it's not quite finished enough. When it hits 1.0, I'll probably give it a more serious look
- woodrowbarlow 2y agoZig is friendly for soft-transitions because the compiler can compile C code. you can use Zig tooling for a C codebase, and then slowly add Zig code where it makes the most sense.
- voidUpdate 2y agoWell I was debating learning C this year, but Zig seems like the more attractive option. I'd be starting new projects from scratch
- johnisgood 2y agoI think it is still useful to learn C, however. For personal projects you could use Zig, but it is still good to know C. C is like the lingua franca of programming languages, and some assembly (if you care about optimization, for example).
- woodrowbarlow 2y agoahh, ideally i'd say learning them in any order is fine but unfortunately since the zig ecosystem is not mature yet that means you'll find yourself using C libraries from zig fairly often. so, yes, i agree learning zig is a great idea for new independent projects but (at the current stage) be prepared to also learn at least enough about C to use C libraries (return type conventions, raw enums, c-style strings, inflexible allocators).
- masklinn 2y ago> Rust is supposed to replace C++, not C, right? Rust is intended as a systems langage. To the extent that it’s “supposed” to replace anything, it’s both.
- kwon-young 2y agoCurl is one of the very few projects I managed to contribute to with a very simple PR. At the time, I was a bit lost with their custom testing framework, but was very imprest by the ease of contributing to one of the most successful open-source project out there. I now understand why. It is because of their rules around testing and readability (and the friendly attitude of Daniel Stenberg) that a novice like me managed to do it.
- MrMcCall 2y agoAll his ideas are fantastic, and are obviously the result of long experience in a seasoned and highly successful project. He is sharing techniques that simply work for large, complex codebases. Ignore them at your peril! Specifically, though, these sections are related, in my experience: > Avoid "bad" functions > Buffer functions > Parsing functions > Monitor memory function use These related aspects are why I tend to wrap many library functions that I use (in any language environment) with my own wrapper function, even if it's to just localize their use into one single entry/use point. That allows me to have one way that I use the function, thereby giving my code a place to not only place all best practices for its use, but to allow me to update those best practices in one single place for the entire codebase. And it is especially helpful if I want to simply rewrite the code itself to, for example, never use scanf, which I determined was a necessary strategy many, many moons ago. Now, when a single function needs to accomodate different use cases and doing such separate kinds of logic would incur too much logical or runtime cost, a separate wrapper can be added, but if the additional wrappers can utilize the cornerstone wrapper, that is the best, if feasible. Of course, all these wrappers should be located in the same chunk of code. For C, especially, wrapper functions also allow me to have my own naming convention over top of the standard library's terse names (without using macros, because they're to be avoided). That makes it easier for me to remember its name, thereby further reducing cognitive load.
- janoelze 2y agoThis is remarkably clear writing — you sense how it was formed by thousands upon thousands of hours spent communicating, really cool.
- kpcyrd 2y ago> We count about 40% of our security vulnerabilities to date to have been the direct result of us using C instead of a memory-safe language alternative. This is however a much lower number than the 60-70% that are commonly repeated, originating from a few big companies and projects. There has been discussion in an Arch Linux internal channel about the accuracy of these classifications. We noticed many advisories contain a "This bug is not considered a C mistake. It is not likely to have been avoided had we not been using C."-disclaimer, but was unclear what the agenda was and how "C mistake" is defined. It was brought up because this disclaimer was also present in the CVE-2025-0665 advisory[0], which is essentially a double-free but on file descriptor level. The impact is extremely low (it's more "libcurl causing unsoundness in your process rather than can-be-exploited-into-RCE"), but it's a direct result of how C manages resources. This kind of bug can also occur in Python, but you're unlikely to find this kind of bug in Rust. Could this bug have occurred with a programming language that isn't C? Yes. Could this bug have been avoided by using a programming language that isn't C? Also yes. [0]: https://curl.se/docs/CVE-2025-0665.html https://curl.se/docs/CVE-2025-0665.html
- uecker 2y agoThe question is: Could such bugs be avoided in C using the right tools and strategies. And the answer is also often: yes. This is why a large component of the argument for switching to other languages is usually that is impossible to avoid such bugs in C even for experts. But I think this argument, while having some small amount of truth to it, also is partially deceptive. One can not simply look at number of CVEs and conclude this, one needs to compare apples to apples and then I find the reality looks differently, e.g. if a simple mitigation for the bug in C could not be used for some reason, but this reason would also prevent the use of another language in the first place, then using this as argument is misleading.
- pornel 2y ago> Could such bugs be avoided in C using the right tools and strategies "right tools and strategies" is very open-ended, almost tautological — if you didn't catch the bug, then obviously you haven't used the right tools and the right strategies! In reality, the tools and strategies have flaws and limitations that turn such problem into "yes but actually no". Static analysis of C code has fundamental limits, so there are bugs it can't find, and there are non-trivial bugs that it can't find without also finding false positives. False positives make developers needlessly tweak code that was correct, and leads to fatigue that makes them downplay and ignore the reports. The more reliable tools require catching problems at run-time, but problems like double-free often happen only in rare code paths that are hard to test for, and fuzzers can't reach all code either.
- dcminter 2y ago> "Wider code is harder to read. Period. " That's stated as if it were proven, and I can believe that it has enough basis in fact that one might choose to enforce it, but I don't believe it's universally true. I do often see code subject to a line-length linting enforcement that I think would have been clearer not broken up across multiple lines. Personally I prefer a linter with escape hatches so that you can declare "this line exempt from such and such a rule" if you have enough reason for it and are willing to take the fight to the pull request :D
- sebstefan 2y agoThe guidelines feel out of sync with the directions I've seen people push coding styles over the years "Identifiers should be short" when I've mostly seen people decry how annoying it is to find yourself in a codebase where everything is abbreviated C-style (htons, strstr, printf, wchar_t, _wfopen, fgetws, wcslen) There's a case for more verbosity and if you look at modern Curl code it reflects that as well, new identifiers aren't short https://github.com/curl/curl/blob/master/lib/vquic/vquic.c https://github.com/curl/curl/blob/master/lib/vquic/vquic.c "Functions should be short" where I've mostly seen very negative feedback on codebases written following the trend of Uncle Bob's short functions. Complaints that hiding code in 10 levels of function calls isn't helpful, and that following rabbit holes is tedious even with modern editors "Code should be narrow", "we enforce a strict 80 column maximum line length" I don't think I've seen that take lately. I remember seeing a few posts fly by about the number 80 specifically You want to prevent dragging your eyes. For my IDE on default settings on a 1080p monitor, half of a 15" screen fits 100 characters If you take away 20 columns to fit your text on less of the screen do you really get any benefits What about the cascading effects on the code, like worse names, split lines, ... In the end it's semi-interesting but we're all building sheds and these are mostly debates on what color the shed should be
- tuetuopay 2y agoEverything is a balance. IMHO, the "identifiers should be short" "functions should be short" and such are knee-jerk reactions to overly long things that are common in some other languages (looking at you, Java). Like the practice of indicating the type, pointer, etc. Stuff ike `pWcharInputBuffer` and such. There is a balance between `*p` and `inputPointerToMiddleOfBufferThatFrobnicates`.
- dahauns 2y ago>Everything is a balance. Very true, or as I like to put it: everything is a tradeoff. Over decades of programming, I'm fairly certain my preferences for things like function/identifier length could be plotted along a damped oscillation curve. :)
- Almondsetat 2y ago
- veltas 2y ago> So many people will now joke and say something about wide screens being available And this is a silly point because I want to be able to put 2-3 files side-by-side, on that big monitor. Who are all these people asking for long code that means I don't get more than one file on screen at a time?
- Arch-TK 2y agoIt's not even just that. The reason newspapers have multiple columns rather than lengthy lines is because it's strictly easier to read shorter lines.
- timhh 2y agoI don't think anyone disagrees with that, but 80 characters is clearly waaay too restrictive. I think 120 is much more reasonable.
- dspillett 2y agoAs the article states: > The question could possibly be exactly where to draw the limit, and that’s a debate for every project to have. It is subjective, and does not live in a vacuum because along with purely subjective preference regarding it on its own, it affects, and is affected by, other choices like naming and indentation conventions. They like 80 in their project. Feel free to choose something else for your project.
- Arch-TK 2y agoClearly to whom? I think it works fine in C with 8 spaces per indent level. It works fine in python and rust with 4 spaces per indent level. For some languages I think it's worth going down to 3 spaces per indent level. But I've not hit that many languages where it's worth going much past 80 characters.
- johnisgood 2y agoI use either 2 spaces or tab for indentation for most languages, and I never go beyond 80 (actually, 79). It works well for XTerm, and most utilities that I use. For git commits, I do not go beyond ~69 characters per line, so it looks neat when I am viewing the commit history. 120 characters may be fine if I only care about coding in VSCodium, for example, so sometimes I might go above the 80 column width when I am programming Go using VSCodium, but I try to not do that, because I still use "less" and whatnot, plus I have an old 17" monitor. I do not like wide monitors, I want to be able to look at the whole screen all at once, with wide monitors I would either have to be too far away, or move my head / neck / eyes too often. So... my fonts are small, I limit to 80 column width, and I am quite happy with it. :P To each their own, although I would have issues with Java code that not only requires me to have many files open, I would have to switch back and forth files, and I would have to horizontally scroll a lot. I hope you realize what I am trying to say, if not, I will elaborate.
- acmj 2y agoSome part of this article is opinionated. Curl may be well written but this is more likely to be the result of the overall structure than the number of characters per line. Actually I don't know whether curl is well written. Popularity doesn't always equate to code quality. I have used curl APIs before. I don't like them.
- timhh 2y ago> We count about 40% of our security vulnerabilities to date to have been the direct result of us using C instead of a memory-safe language alternative. This is however a much lower number than the 60-70% that are commonly repeated, originating from a few big companies and projects. If this is because of a difference in counting or us actually having a lower amount of C problems, I cannot tell. When I counted I got about 55% which is pretty close to the standard 2/3. https://blog.timhutt.co.uk/curl-vulnerabilities-rust/ https://blog.timhutt.co.uk/curl-vulnerabilities-rust/
- bitwize 2y ago> how do we write C in curl to make it safe and secure for billions of installations? "That's the neat thing -- you don't." Curl should do what fish did: bite the bullet and rewrite the damn thing in Rust.
- guappa 2y agoThat would mean no longer running on a lot of devices it currently runs on. Which would mean those devices would just use extremely out of date curl written in C. Any other brilliant idea?
- deleted 2y ago[deleted]
- kobzol 2y agoGreat post! I have some random guesses as to why the 40% vs 60-70% memory issues percentage: - 180k is not that much code. The 60-70% number comes from Google and Microsoft, and they are dealing with way larger codebases. Of course, the size of the codebase in theory shouldn't affect the percentage, but I suspect in practice it does, as the larger the codebase is, the harder it is to enforce invariants and watch for all possible edge cases. - A related aspect to that is that curl is primarily maintained by one person (you), or at most a handful of contributors. Of course many more people contribute to it, but there is a single maintainer who knows the whole codebase perfectly and can see behind all (or most) corners. For larger codebases with hundreds of people working on them, that is probably not the case. - Curl is used by clients a lot (probably it's used more by clients than servers, for whatever definition of these words) over which you have no control and monitoring. That means that some UB or vulnerabilities that were triggered "in the wild", on the client side, might not ever be found. For Google/Microsoft, if we're talking about Chrome, Windows, web services etc., which are much more controled and monitored by their companies, I suspect that they are able to detect a larger fraction of vulnerabilities and issues than we are able to detect in curl. - You write great code, love what you're doing and take pride in a job done well (again, if we scale this to a large codebase with hundreds of developers, it's quite hard to achieve the same level of quality and dedication there). (sent this as a comment directly on the post, but it seems like it wasn't approved)