9 ms·
Lessons learnt while trying to modernize some C code (2022)
- smcameron 4y ago> C has a type system, but the developers are ignoring it, preferring to move stuff around as void *. Obviously, it shouldn’t be necessary to do that, and this was one of the things I wanted to fix by moving the code into a C++ compiler. > This is a case of one function infecting the entire code base. In this case, a function that is often invoked, the xmalloc family, a wrapper over malloc-like functions that return pointers to void. This makes no sense to me. In C, you can assign a void pointer to any typed pointer, no casting required, i.e.: struct foo *f = malloc(sizeof(*f)); The answer here isn't to rewrite it all in C++, but to learn C in the first place.
- void_ptr__ 4y agoThey may be referring to xmalloc(3). What you said is true however they are transitioning to a C++ compiler so your complaint is off-base.
- masklinn 4y ago> however they are transitioning to a C++ compiler OP says they want to transition to a C++ compiler but it's not clear why, the very first issue is not actually one. As GP notes, you don't need to cast out of malloc and you need a macro even less.
- quietbritishjim 4y agoWhen the article says that implicit conversations from void* "shouldn’t be necessary" they seem to mean they feel that, philosophically, it ought not to be necessary. Certainly it doesn't protect against obvious compile time mistakes, which ought to be the point of a type system: /* Compiles in C */ int* x = malloc((sizeof)short) // Does not compile in C++ int* x = new short
- ginko 4y agoI mean sure, but that seems like a minor thing compared to the codebase passing around void*'s left and right. OP could have tackled that first while sticking with C. I've had a brief look over the goaccess source code. It's 37kloc of pretty average looking C. If you think that's even close to the most complicated software projects written in C then I don't know what to tell you.
- bornfreddy 4y agoLink if anyone else is curious (and lazy) : https://github.com/allinurl/goaccess https://github.com/allinurl/goaccess EDIT: looked at the code... I don't see void* being passed around, data types are used, no excessive #ifdefs, it looks like a pretty clean C app to be honest. Maybe the problem is that OP is used to C++? (as the advice to use a special struct for strings would indicate) Either way, goaccess project looks awesome.
- mananaysiempre 4y agoWhat are the typing-related issues xmalloc has that plain malloc doesn’t? As far as I can see, they’re equivalent in that respect.
- bitwize 4y agoNo. For a project like this you absolutely want the stronger type guarantees and abstractions that C++ gives you. Were it me, I'd RIIR, but C++ that uses the type system and const-correctness in the proper way is an improvement.
- WalterBright 4y agoConst-correctness doesn't actually work in C++. It's more of a documentation aid. (It doesn't work because it can be cast away. It's also not transitive, so is ineffective with generic template code.) D code won't allow const to be cast away (unless in system code), and is transitive. Sometimes people complain that this is too harsh, but it's reliable and effective.
- maccard 4y agoI can't believe I'm about to try and correct you of all people on this but you can't just const_cast away[0]. The actual problem is that you have no guarantee that what you're being told is const is actually const. If you have a const ref to an object, that doesn't mean it's actually initialised as const, for example. [0] well of course technically you can, but casting a CV qualifier away and modifying it is UB.
- WalterBright 4y agoYes, you are correct, it is UB. Try this code: void foo(const int * const p) { *(int*)p = 3; // UB? So sue me! } It compiles with g++ and clang++ with no warnings or errors. gcc and clang, too. Sure, it's UB, but the compiler won't even warn you. I.e. it serves no purpose, other than documentation. Let's try it in D: @safe void foo(const int *p) { *cast(int*)p = 3; } Error: cast from const(int*) to int* not allowed in safe code
- maccard 4y agoI think we're in agreement here, but speaking past each other! Your snippet isn't actually UB though, it's perfectly fine. See [0] > I.e. it serves no purpose, other than documentation Yeah, agreed. [0] https://gcc.godbolt.org/z/v4qYME8fP https://gcc.godbolt.org/z/v4qYME8fP
- deleted 4y ago[deleted]
- shrimp_emoji 4y agoOne problem is that, when you cast to `void*`, you discard size information. I have a `double*`, I know the size of the object behind that pointer is `sizeof(double)`. If I have a `void*`, and nothing else, all bets are off.
- masklinn 4y agoOP is saying that you don't need an explicit cast to cast from a void* (which is what malloc returns).
- shrimp_emoji 4y agoIn that case, I guess >C has a type system, but the developers are ignoring it, preferring to move stuff around as void *. is alleging that C programmers insist on literally keeping their allocated variables behind `void*`s. That's too weird to me. :p Because, yeah, you don't need an explicit cast from a `void*` returned from a `malloc`, and I assume everyone tacitly assigns that `void*` to a `Foo*` immediately. The only case where you're converting stuff to `void*`s is when passing/taking it to/from "generic" APIs, where it could be any type. (Hence "move stuff around".) I thought OP was alleging there's no problems with this practice, although that interpretation of mine doesn't make sense from what they said in retrospect.
- wizofaus 4y ago"Some non-zero multiple of sizeof(double)", surely?
- mananaysiempre 4y agos/non-zero // double x[1], *p; p = x + 1; /* legal though *p wouldn’t be */
- wizofaus 4y agoSurely in that case x[0] is the object "behind" the pointer (assuming adding 1 puts you ahead!). But using the orginally intended meaning of "behind" here, logically there is no object at all...
- Gibbon1 4y ago> move stuff around as void * People do that because they don't understand how to define and use opaque pointers. Also if someone would put the C standards committee on an ice flow and replace them with people willing to add first class types and tagged unions to C that would be nice.
- gsinclair 4y agoice floe*
- avinassh 4y ago> People do that because they don't understand how to define and use opaque pointers. what are opaque pointers?
- masklinn 4y agoPointers to a well-defined type, but one which does not reveal the underlying datatype so a user is unable to interact with the actual data without going through the library’s exposed API (or at least strongly disincentivised from doing it). IIRC the normal way to do that in C is to use an incomplete type, aka just have a struct foo; In the header file and define the actual struct contents in the implementation file.
- Gibbon1 4y agoYou can create a pointer with a type but no storage definition. // foo_t is opaque typedef struct foo_s foo_t; // function defined taking an opaque pointer int Baz(foo_t *f); // actual definition typedef struct foo_s { int bar; } foo_t; // function def int Baz(foo_t *f) { return f->bar; }
- jalino23 4y agoI just recently started learning c and having hard time wrapping my head around third party libraries. Coming from Node/npm I’m used to the idea that your dependencies gets bundled in your app. But its not like that on C. you expect your users to install your app dependencies for you, and you have to figure out the path of your own dependencies. cause they don’t live in one nice place like node_modules. and I haven’t even began to figure out how Im supposed to know what linker flag to use for the library I use. the tutorials just give it to you without explaining where they get it from. and most of the sources I’ve read almost always recommends dynamic linking cause static is heavy. which brings me back to this article that now recommends static linking and I agree with that. much of the learning materials I’ve come across are just spoon fed to you without explaining why. And its so incredibly frustrating until you look at linux history, most of the assume knowledge now is based on baggage upon baggage of layers of history.
- zabzonk 4y agolearn c++ instead - despite what you may have heard, it is much easier to learn than c. but neither language (and they are very different) has standardised library/package tools.
- MattPalmer1086 4y agoI don't think this is good advice, although I don't agree with you being downvoted because of that. C++ is a much more complex language than C. The things you need to worry about in C are far fewer than in C++.
- zabzonk 4y agook. a small challenge - read character input of undefined length, and then print the input out backwards. i guarantee the c++ code will be easier to write and understand than any c code.
- camel-cdr 4y agoThat actually sounds like a fun way to explore different programming languages. Especially if you extend it to inputs that may take up more memory then you've got on your computer (let's say 100GB) and at least reasonably fast. I suppose it'd kinda trivial if you read the input from a regular (seekable) file, but if you actually read the 100 GB from stdin then this could get quite complex.
- zabzonk 4y agocompletely agree with this. strong typing is why i made the change from c to c++, and have never looked back. and of course malloc makes no sense in c++. as constructors will not be called on the types you are allocating - and let's not get started on free(). reply
- uecker 4y agoIn what sense is C++ more strongly typed than C?
- zabzonk 4y agoin just about every sense. when K&R wrote the 2nd ed of The C Programming Language, they ran all their examples through Stroustrup's then new C++ compiler (there was no ANSI C compatible compiler at the time) - the number of type problems it diagnosed shocked them.
- uecker 4y agoI meant today, not in the past when C did not yet have prototypes. This is irrelevant today.
- zabzonk 4y agowell. the classic is:+- int * p = malloc( sizeof(int) ); which is horrible in either language, but is legal c, but not c++
- uecker 4y agoIn C you could write or have a macro which does the cast. int *p = malloc(sizeof *p); In C++ you would have to add an explicit cast, which would not make it safer: int *p = (int*)malloc(sizeof char); So from a type system perspective this is not safer.
- 4y ago
- deleted 4y ago[deleted]
- pengaru 4y ago> C has type system TIL, apparently all that gobject code attempting to add something resembling a type system to C in glib is redundant... Edit: After taking a quick glance at some random listings in goaccess, I don't have the impression this was written by particularly talented C developers. Example: https://github.com/allinurl/goaccess/blob/master/src/sort.c#L489 https://github.com/allinurl/goaccess/blob/master/src/sort.c#...
- int0x80 4y agogobject is trying to add OOP to C. C already has a type system indeed, however it can be considered weakly typed.
- pengaru 4y agoCalling the weak type checking of C a "type system" is an incredible stretch.
- mtlmtlmtlmtl 4y agoC has a static type system. You can call it limited and you'd be right. But to say it's not a type system is just silly, and just makes the term more confusing. There are types, they're checked, there's a type system.
- pengaru 4y ago"type system" actually means something substantial in the era of C's relevance, and C specifically didn't have one. But nobody apparently cares about the in-context meaning of these words anymore. Operating system, UNIX system, the system() function, system calls are costly, try find any mention of type system in the K&R C book [0]. Search for the word system, it's always in the context of something relatively huge, complex and usually runtime-dynamic vs. the C language. To call C's type checking a type system is to completely misunderstand something fundamental about the language. [0] https://ia803407.us.archive.org/35/items/the-ansi-c-programming-language-by-brian-w.-kernighan-dennis-m.-ritchie.org/The%20ANSI%20C%20Programming%20Language%20by%20Brian%20W.%20Kernighan%2C%20Dennis%20M.%20Ritchie.pdf https://ia803407.us.archive.org/35/items/the-ansi-c-programm...
- deleted 4y ago[deleted]
- isomorphic- 4y agoC uses `declaration follows use` for its declaration syntax. This means that instead of `char* p` we write `char *p`. https://www.quora.com/Why-doesnt-C-use-better-notation-for-pointers/answer/Brian-Bi https://www.quora.com/Why-doesnt-C-use-better-notation-for-p... Also, "decay" is a terrible way to describe the conversion of arrays to pointers-to-their-first-element. "Decaying" implies permanence and that the array is changing/decaying. This isn't the case. The array doesn't permanently change into a pointer. It is the expression that is converted rather than the array itself. This article is terrible.
- frutiger 4y agoI can’t speak for the rest of the article but for better of for worse “decay” is the standard word used for what happens to arrays as they are passed around as pointers. See for instance https://en.cppreference.com/w/cpp/types/decay https://en.cppreference.com/w/cpp/types/decay.
- skribanto 4y ago`char *p` is identical to `char* p`??
- peterfirefly 4y agoYes, but "char* p,q;" is "char *p,q;" -- q is a char, only p is a pointer.
- jacquesm 4y agoYes, so don't do that. Do: char * p; char q;
- WalterBright 4y agoIn D we write it "char* p, q;" because both p and q are pointers.
- quietbritishjim 4y ago
- leni536 4y agoThe combination of stuff like char*** and the lack of const usage by some C code is really horrible. Like, yeah, maybe you do need something like char***, but do you really need that to be mutable at every single indirection level? But even if you sometimes see char***, you really never see char const* const* const*.
- bsder 4y agoActually, I put that kind of thing in my code all the time. However, you will see "char **" as "char const * const * const" in my code. Yes, newer languages understand "char const * const" as "const String". And, yes, it stinks for brevity, but I'm not on a 300 baud TTY.
- tpmx 4y agoSomewhat off-topic: goaccess (the tool the author is trying to work on, which isn't written in Go but rather C) does look very appealing. https://goaccess.io/features https://goaccess.io/features All panels and metrics are timed to be updated every 200 ms on the terminal output and every second on the HTML output. 2021: https://news.ycombinator.com/item?id=28012307 https://news.ycombinator.com/item?id=28012307 2019: https://news.ycombinator.com/item?id=21890027 https://news.ycombinator.com/item?id=21890027 2016: https://news.ycombinator.com/item?id=13211913 https://news.ycombinator.com/item?id=13211913
- jvanderbot 4y agoVery relevant article for the times. I sympathize that nowadays people don't have the same expectations as the previous generations. But pretty much all of these are just learning curve issues. C is fine, it's just very different than today's languages, and I'm glad we're improving the ecosystem.
- WalterBright 4y ago> Add a string type and eradicate the zero terminated strings. I tried that several times in my C code. I always wound up reverting to 0 terminated strings, for the simple reason that everything else in the C ecosystem is 0 terminated strings. For example, printf.
- db48x 4y agoYes, you still need to be able to use null–terminated strings at the edges, when you read them in or print them out. But inside your program, resizable strings that know their own length and capacity are a huge benefit.
- WalterBright 4y agoYes, that's why I'd make my own string type. But it always turned out to be more trouble than it was worth, because every other piece of C code wanted to use 0 terminated strings.
- WalterBright 4y ago> C’s portability is a joke between #ifdefs and #endif. Ironically, I just spent a couple days trying to get the C11 Standard .h files on various platforms to compile with ImportC (D's C11 C code importer). Every single platform D supports fails to have a way to compile their .h files using a Standard C11 compiler, each in its own unique, peculiar ways. They all rely on their own C compiler extensions. Some make an attempt with #ifdef/#ifndef to be portable, but they all fail.
- greatgib 4y agoit’s a demonstration why you shouldn’t use C to write complex projects. Not! It is a demonstration why you need good competent developers for a complex project and not script kiddies.
- notbeuller 4y agoI'd like to +1 the advocates of -x c++ here. There's just no reason not to. It's like building scaffolding around a building so you can retrofit it. It doesn't need to end up like a tower of c++, but the scaffolding lets you hoist things out of overly complex codebases a lot easier. The only immediate code changes encountered were adding explicit casts from malloc (and fixing actual bugs that had been hidden.) In one case recently, trivially converting some c structures to c++ and hiding internal representations let me delete thousands of lines of set(foo, xxx) calls because I could prove that there was no access to the computed result. However, because I'm inherently chaotic neutral, I tend to use -x objective-c++, but that's a story for another day.
- uecker 4y agoOne can hide internal representations just fine in C using pointers to incomplete struct types with the definition of the struct and the implementation of the functions operating on it in a separate file. I like this much better than what C++ does, because only the interface and not also implementation details end up in the header. This keeps things nicely separated and built times very short. I see no real benefit from -xc++ and I would also miss some stuff such as variably modified types, designated initializes, etc.
- _gabe_ 4y ago> goaccess is pretty neat for a C project, and it’s a demonstration why you shouldn’t use C to write complex projects Or , it's an example of how not to write a complex project in C. None of the issues the author points out are issues with C in general, they're issues with how the developers coded the project. The only issues the author brings up are: * They don't use the type system * They don't use const correctness (although I'm not sure how C const differs from C++ const if it does at all) * They use unsafe functions like strlen instead of custom string types * They complain about the lack of container types (but that's just an issue with C in general and you'll either have to forego type safety and use ugly macros or rewrite a ton of code afaik) * They complain about portability via #ifdefs and then complain that code paths that aren't executed aren't going to be tested (I don't know what the author is expecting here. If you have tests you can use them, but if you have no way to run the tests for platform X, but somebody added the code once upon a time, what's wrong with leaving the code and putting a note that says YMMV. Also, what does #ifdefs have to do with this? Whether platform code is locked away in a #ifdef or an abstract interface that never gets tested, it's the same problem) * They complain about dynamic linking. But that's something you can avoid if you want by statically linking like they mention There are plenty of good reasons to hate on C, but all of the problems listed above can (and usually are) avoided by C devs, so this feels like a hollow argument. I was interested in hearing some of the challenges that the author ran into while trying to upgrade a C project to a modern language, but it sounds like they didn't even do that. They just complain about this particular projects dev style, which is fine, but it's not what the title says this post is about.
- rurban 4y ago> CXXFLAGS: "-std=c++20 -g3 -O2 -flto -Werror" -Werror without -Wall or -Wextra is pretty lame. It just errors on all suppressed warnings.