9 ms·
Declaring C String Constants
- tonyg 9y agoInterestingly, we get .LC0: .string "Lorem ipsum" [...] movl $.LC0, %esi movl $ptr2, %edi xorl %eax, %eax jmp bogus for the same experiment done with a const pointer to const data, const char * const ptr2 = "Lorem ipsum"; (Incidentally, this kind of thing is why I prefer to write 'char const STAR const' rather than 'const char STAR const', and 'char const STAR' rather than 'const char STAR'; it systematically places the 'const's.) (PS. HN really doesn't work well for trying to write either unicode or ascii star characters.)
- a_t48 9y agoHence the need to compile with -O2 rather than -O3, I would assume.
- smilekzs 9y agoHad parent been a StackOverflow post, this would be the undisputed correct answer.
- bsder 9y agochar const * const ptr2 = "Lorem ipsum"; Edit: Said exact same thing as parent but formatted differently. Sorry. :)
- swolchok 9y agoOr you could just #define STR "Lorem Ipsum" and be sure that it's going to be efficient.
- std_throwaway 9y agoThis is a joke, right?
- DSMan195276 9y agoWhy would it be a joke? Using `#define` for a string constant is perfectly reasonable for C code, IMO I'd argue it's the idiomatic and best way to do it. String constants that are `#define`d can take advantage of C's automatic string-literal concatenation, allowing you to avoid lots of unnecessary string operations in some cases. If you declare it as a pointer or array instead, then you can't do that.
- quotemstr 9y ago> Why would it be a joke? A certain influential segment of the C and C++ development community, the kind that tends to worship "best practices" and "consistency", has been preaching for years that C preprocessor macros are evil, even in cases where they allow for much clearer code. I'm not sure these people were born with the ability to experience joy.
- marcosdumay 9y agoThe biggest problem with C macros is that they are too opaque. The compilation stack will complain about code that is different from what you have, and leave you with little hint about what in your code is generating the broken code. It's also confusing when debugging. That said, yes they can lead to large gains so when they are worth it, go for them. Just mention in the documentation that they are macros, in big letters.
- magnetic 9y ago> Just mention in the documentation that they are macros, in big letters. The macros themselves are usually in BIG_LETTERS already... that generally gives it away.
- microtherion 9y agoAnd you pay for that with global namespace pollution and utter confusion if you ever reuse that name in a scope where you didn't even know you included the original header file.
- contravariant 9y agotemplate<char ... chars> struct const_string { static std::string get() { return std::string { chars ... }; } }; Please don't do this.
- Analemma_ 9y agoAny programming language that needs an entire blog post about how to declare string constants "the right way" is a programming language that needs to disappear.
- torstenvl 9y agoAny poster that needs to post comments about how any language requiring real understanding needs to disappear needs to disappear.
- irundebian 9y agoHaha, real understanding. Any language which requires a standard of 700 pages should be seem suspicious. And any language, which lead to thousands of vulnerabilities needs to disappear.
- seibelj 9y agoC is not that far removed from assembly, and gives the programmer strong control over how the program operates. It is a tool, and if you need safety over control / performance / portability / etc., then don't use C.
- irundebian 9y agoBeing a language not far form assembly shouldn't mean being broken and error prone.
- seibelj 9y agoI'm not sure what you mean by "broken". The programmer must provide 100% correct code, as in any language. It is harder to write secure code in C than in other languages, and in return you get near-maximum control over the program.
- irundebian 9y agoThe point is, if it's possible to have maximum control over the program without hardness of writing secure code. And it is, as Rust and Ada are showing.
- carlsonmark 9y agoWhile I do enjoy this sort of analysis, I feel the decision to choose one method over another should be based on benchmarks (preferably with more than one size of string.) After benchmarking, then do an analysis of the assembly code. Guessing about pipelining, memory access times, and the effect of generated code size is much less valuable than real measurements.
- a_t48 9y agoBeyond this, &ptr and &arr are different types! The signature for bogus(arr, &arr) is something like void bogus(const char* arg1, const char (*arg2)[12]) While they do point to the same data, it's pretty rare to actually want to declare a const char (*)[N] as a function argument - template magic code excepted, you don't gain much over a char * and it's not the same as a char** The two lines do different things, so of course they will generate different code. What do you know, the one that actually lets you do useful things is a few bytes longer.
- droithomme 9y agoArrays and pointers are not the same in C, nor have they ever been. The difference is more than that of mere syntax choices for equivalent concepts. In the pointer version of the code, the pointer is reassignable. Only the data it references can't be modified, the pointer can. To prevent the pointer from being modified, the declaration should have been: const char * const ptr = "Lorem ipsum"; With the array version, there is no pointer to be modified and so no need or ability to have a second const.
- eridius 9y agoAll this talk about do_ptr being slower because it copies from main memory is misleading. Yes, it's slower, because it's doing something different than what do_arr does. It's not just a slower version of doing the same thing. So trying to compare do_ptr and do_arr makes no sense. And the whole thesis of this piece, that declaring string constants as arrays is better, has no supporting evidence. You know what's better? Don't dereference a pointer if you don't need to. That's it.
- sqeaky 9y agoWasn't that sort of the point or using arrays instead of pointers? There is less dereferencing to get to the data, so you need to dereference fewer pointers. I would like to see more empirical measurement in articles like this. There has to be a reason both compiler decided to emit that pointer based code, surely there is some advantage.
- a_t48 9y agoThey are the same thing, in most cases. It's just that &arr and &ptr are different types.
- eridius 9y ago> Wasn't that sort of the point or using arrays instead of pointers? There is less dereferencing to get to the data, so you need to dereference fewer pointers. No, and that's why the article is confusing. It makes it seem like that's true but it's really not. If you remove everything else and just look at passing the pointer itself, the fundamental difference between the pointer version and the array version is the pointer version isn't constant, and therefore has one level of indirection. This is because you can reassign to the `ptr` variable. But the array is constant, you can't reassign `ary`. All you really have to do here is put `const` on the `ptr` variable and suddenly you get the exact same code. Or in other words, const char * const ptr = "Foo"; behaves the same way as const char ary[] = "Foo"; And the only practical difference between the two at this point is how the sizeof() operator works.
- deleted 9y ago[deleted]
- mark-r 9y agoI wonder how the results would have differed if those were automatic variables instead of static? That's a much more real-world situation. You wouldn't be able to ignore the overhead of re-initializing the array, since it would be occurring at run time instead of compile time.
- iainmerrick 9y agoThere's a big mistake here: // Bogus function, just to see how arguments are passed. void bogus(); // Invoke bogus using ptr. void do_ptr() { bogus(&ptr, ptr); } // Invoke bogus using arr. void do_arr() { bogus(&arr, arr); } "&ptr" and "&arr" are not the same. &ptr gives you a pointer to a pointer (char * *), but &arr just gives you a pointer to the array data. That's why the code is different. This would normally show up as a compilation error, since &ptr and &arr have different types. It's disguised here by the variadic bogus() function. (I didn't know that no-arg functions in C are implicitly variadic! That's bizarre. I would have expected modern compilers to disable that by default, but I can't get Clang to warn me about it even with -Wall.)
- 0xffff2 9y ago>(I didn't know that no-arg functions in C are implicitly variadic! That's bizarre. I would have expected modern compilers to disable that by default, but I can't get Clang to warn me about it even with -Wall.) I didn't know that either. I wonder if `void bogus(void)` behaves any differently?
- kccqzy 9y agoYes it would behave differently. Putting void in the parenthesis tells the compiler the function accepts no arguments at all.
- 0xffff2 9y agoGood to know. I never bothered with `void` inside the argument list in C before (I primarily write C++, very rarely C). I will have to remember to be more explicit when I do write C code in the future.
- Koshkin 9y agoYes, in C you must use 'void' to indicate that the function takes no arguments. (This is deprecated in C++ in favor to an empty argument list - which breaks backward compatibility with C).
- mnarayan01 9y agoAs others have noted, this is a little bizarre as the two functions are doing different things; one being less performant than the other is not really surprising. That said, there can be advantages to using array syntax rather than pointers for strings (even when also declaring the pointer to be constant), e.g. the compiler "knows" the array "pointer" is non-null: extern const char arr[]; void do_arr() { if (arr) { dummy(); } } allows the compiler to optimize out the conditional: https://godbolt.org/g/FwBeWx https://godbolt.org/g/FwBeWx.
- kccqzy 9y agoAnother difference: if you use sizeof, the pointer version gives you the size of a pointer, but the array version gives you the actual number of bytes in the string.
- petters 9y agoThe author took the time to write a blog post, but not trying the code for anything else than that bogus() function. Had they done that, they would have realized their mistake.
- russdill 9y agoOther comments have already deconstructed much of the article, but I'll add this. The primary factor will be your CPU architecture. Can you put the string in the same cacheline as the code or an adjacent cacheline? If so, awesome. However, if you have a microcontroller with a harvard architecture, such as a cortex M3, it's may be better to put your strings in data memory rather than code memory. The processor can simultaneously load your string and the next instructions via the two memory ports.
- bsder 9y agoWhat I find funny is that through all this he never actually mentioned the fact that sizeof() a string constant includes the NUL character at the end. This has bitten me more times than I care to admit. Nowadays, I almost always use the array form and actually declare my strings character by character so that I see the NUL if I actually meant to use it.