5 ms·
It's really not fair to compare the legibility because that code is not at all doing the same. He's basically comparing his naive "one byte at a time" strlen t
by revelation 10y ago
It's really not fair to compare the legibility because that code is not at all doing the same.
He's basically comparing his naive "one byte at a time" strlen to one at least processing at register size. The naive version looks just as good in C, frankly better, because what is this "0.." offset nonsense.
- lmm 10y ago> The naive version looks just as good in C, frankly better, because what is this "0.." offset nonsense. I'm pretty sure "for i in 0.." is clearer (to a reader unfamiliar with either) than whatever it is you'd do in C. In C it would be something like "for(int i=0;;i++)", right?
- pbsd 10y agoI'm pretty sure a simple while loop is better than either: size_t strlen(const char * s) { size_t index = 0; while(s[index] != 0) { index += 1; } return index; }
- lmm 10y agoThat's substantially harder to follow than the code in the article - you have to jump around to see where index changes and how that interacts with the loop condition.
- matthewaveryusa 10y agoJust out of curiosity how would you rank strlen{1,2,3} in terms of difficulty to read? size_t strlen1(const char * s) { const char* it =s ; while(*it != '\0') { ++it; } return it - s; } size_t ptr_distance(const void *begin, const void* end) { return end - begin; } size_t strlen2(const char * s) { const char* it =s ; while(*it != '\0') { ++it; } return ptr_distance(s,it); } int is_null(const char* p) { return *p == '\0'; } const char* find_first(const char* s, int (*predicate)(const char*)) { while(1) { if(predicate(s)) { return s; } ++s; } } size_t strlen3(const char * s) { return ptr_distance(s,find_first(s,is_null)); }
- kbenson 10y agoAs someone whos' written almost no C in over a decade, and read minimal C in that time, I would say strlen1 is clearest because it's so simple and succinct, mainly because of how simple the underlying algorithm is. strlen3 is easiest to read, as you can make fairly valid assumptions about the helper functions, and easily confirm them if needed, and it very clearly breaks down the algorithm into its conceptual components. I think it would also be clearest (and thus winner) if the algorithm was slightly more involved, as the gains in clarity would outweigh the gains of having everything defined in just a few lines. strlen2 is worst, because the helper function gains you very little for the cognitive overload of having the implementation being somewhere else. You are just as well served in this case by a trailing comment, IMO.
- ThatGeoGuy 10y agoI agree with this comment more or less, however I would like to point out that strlen{1,2,3} are all wrong, at least in the capacity that they take pointer differences and return a size_t. In truth, `ptr_distance` and `return it - s;` should both be of type ptrdiff_t, which is not necessarily the same as size_t. In some ways this is why using an explicit index of type size_t that can be incremented is better, because you can avoid some type casting.
- lmm 10y agoI would say strlen3 is easiest to read (assuming we're in a codebase that makes widespread use of find_first, ptr_distance and is_null), then strlen1, then strlen2. I think thinking in terms of ptr distances is still needlessly confusing. It's hard to express what we need directly in C (even the Rust version is using nonlocal return which is not the best thing for readability - we could emulate it with goto but that has its own issues) where we don't have generics, anonymous functions, multiple return or tail call elimination. I mean what I'd write in my head (or another language) translates into C as something like: typedef struct { boolean continue; size_t value; } result_size_t; int indexed_cata_size_t( const char *s, void (*operate)(const char, size_t, result_size_t*)) { const char *it = s; size_t idx = 0; // May get initialization syntax slightly wrong but you get the idea result_size_t result; result.continue = true; while(result.continue) operate(it++, idx++, &result); return result -> value; } void index_of_first_null_u(const char c, size_t idx, result_size_t *result) { if(c == '\0') { result -> value = idx; result -> continue = false; } } size_t strlen4(const char *s) { return indexed_cata_size_t(s, index_of_first_null_u); } but I'm not sure that ends up any clearer.
- deleted 10y ago[deleted]
- burfog 10y agoHere is the way that is correct and obvious to a modern C programmer. Real C libraries use assembly and/or multi-byte methods that violate aliasing rules. size_t strlen(const char *restrict const s){ const char *restrict t = s; while(*t++) ; return t-s-1; }