I'm starting with this code:
void func1() {
char tmpfile[] = "/tmp/tmpXXXXXX";
mkstemp(tmpfile); // Note: mkstemp modifies the char array, cannot be const
...
}
void func2() {
char tmpfile[] = "/tmp/tmpXXXXXX";
mkstemp(tmpfile); // Note: mkstemp modifies the char array, cannot be const
...
}
I'd like to refactor this to pull out the shared "/tmp/tmpXXXXXX"
constant. Here is an attempt:
constexpr char kTmpfile[] = "/tmp/tmpXXXXXX";
void func1() {
char tmpfile[] = kTmpfile;
mkstemp(tmpfile); // Note: mkstemp modifies the char array, cannot be const
...
}
void func2() {
char tmpfile[] = kTmpfile;
mkstemp(tmpfile); // Note: mkstemp modifies the char array, cannot be const
...
}
However, this doesn't compile. Changing tmpfile[]
to tmpfile[sizeof(kTmpfile)]
also doesn't work.
The below does work, but it uses a macro which is discouraged by my company's style guide (which is based on the Google Style Guide).
#define TMPFILE "/tmp/tmpXXXXXX"
void func1() {
char tmpfile[] = TMPFILE;
mkstemp(tmpfile); // Note: mkstemp modifies the char array, cannot be const
...
}
void func2() {
char tmpfile[] = TMPFILE;
mkstemp(tmpfile); // Note: mkstemp modifies the char array, cannot be const
...
}
Is there any way to write this "nicely"? Without having to use a macro or hardcode the size? Or is the macro the best option for readability and maintainability?
You can initialize a one-dimensional character array by specifying: A brace-enclosed comma-separated list of constants, each of which can be contained in a character. A string constant (braces surrounding the constant are optional)
Initializing Char Array A char array can be initialized by conferring to it a default size. char [] JavaCharArray = new char [ 4 ]; This assigns to it an instance with size 4.
char* const says that the pointer can point to a char and value of char pointed by this pointer can be changed. But we cannot change the value of pointer as it is now constant and it cannot point to another char.
Initializer List: To initialize an array in C with the same value, the naive way is to provide an initializer list. We use this with small arrays. int num[5] = {1, 1, 1, 1, 1}; This will initialize the num array with value 1 at all index.
Here are three approaches. Credit goes to @πάνταῥεῖ, @PSkocik, and @Asu for suggesting these, I just typed them up.
Approach 1a
constexpr auto kTmpfile = "/tmp/tmpXXXXXX";
void func1() {
std::string tmpfile = kTmpfile;
mkstemp(tmpfile.data());
...
}
Advantages:
Disadvantages:
std::string::data
returns a const char*
in C++14 and earlier (of course, you can use a const_cast
in C++14, but that's also bad)Approach 1b
constexpr auto kTmpfile = "/tmp/tmpXXXXXX";
void func1() {
std::string tmpfile = kTmpfile;
mkstemp(&tmpfile[0]);
...
}
Advantages:
Disadvantages:
Approach 2
constexpr char kTmpfile[] = "/tmp/tmpXXXXXX";
void func1() {
char tmpfile[sizeof(kTmpfile)];
memcpy(tmpfile, kTmpfile, sizeof(kTmpfile));
mkstemp(tmpfile);
...
}
Advantages:
Disadvantages:
As long as the char arrays are local, you can replace
char tmpfile[] = STR_LITERAL;
with
char tmpfile[sizeof kTmpfile]; memcpy(tmpfile,kTmpfile,sizeof tmpfile);
with theoretically no loss of efficiency.
Clang, for example, compiles both func1 and func2 in the snippet below into the same instructions on x86_64:
#include <stdlib.h>
int func1() {
char tmpfile[] = "/tmp/tmpXXXXXX";
mkstemp(tmpfile);
}
#include <string.h>
const char kTmpfile[] = "/tmp/tmpXXXXXX";
int func2() {
char tmpfile[sizeof kTmpfile];
memcpy(&tmpfile,kTmpfile,sizeof tmpfile);
mkstemp(tmpfile);
}
Assembly output:
func1(): # @func1()
subq $24, %rsp
movabsq $24866934413088880, %rax # imm = 0x58585858585870
movq %rax, 15(%rsp)
movabsq $8101259051807896623, %rax # imm = 0x706D742F706D742F
movq %rax, 8(%rsp)
leaq 8(%rsp), %rdi
callq mkstemp
func2(): # @func2()
subq $24, %rsp
movabsq $24866934413088880, %rax # imm = 0x58585858585870
movq %rax, 15(%rsp)
movabsq $8101259051807896623, %rax # imm = 0x706D742F706D742F
movq %rax, 8(%rsp)
leaq 8(%rsp), %rdi
callq mkstemp
This solution for refactoring the duplicate initialization string without using macros also works in plain C.
Using std::string
, while it would generally use the heap, which is quite a bit more expensive than the stack, shouldn't hurt here much either as you can expect the file creation to take at least a microsecond and thereby dominate over the heap allocation and string copy.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With