Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Initializing char array from constant

Tags:

c++

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?

like image 217
Kerrick Staley Avatar asked Jan 30 '19 21:01

Kerrick Staley


People also ask

How do you initialize a char array?

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)

How do you initialize a char array in Java?

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.

Can const use char?

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.

How do you initialize an entire array with value?

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.


2 Answers

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:

  • less code / more readable

Disadvantages:

  • C++17 only, because 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)
  • may be slower, since char array may be allocated on the heap instead of stack

Approach 1b

constexpr auto kTmpfile = "/tmp/tmpXXXXXX";

void func1() {
  std::string tmpfile = kTmpfile;
  mkstemp(&tmpfile[0]);
  ...
}

Advantages:

  • less code / more readable
  • C++11 and later (see Does "&s[0]" point to contiguous characters in a std::string?)

Disadvantages:

  • may be slower, since char array may be allocated on the heap instead of stack

Approach 2

constexpr char kTmpfile[] = "/tmp/tmpXXXXXX";

void func1() {
  char tmpfile[sizeof(kTmpfile)];
  memcpy(tmpfile, kTmpfile, sizeof(kTmpfile));
  mkstemp(tmpfile);
  ...
}

Advantages:

  • only uses stack, no heap
  • compatible with C++14 and earlier

Disadvantages:

  • more verbose / less readable
like image 162
Kerrick Staley Avatar answered Sep 20 '22 22:09

Kerrick Staley


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.

like image 41
PSkocik Avatar answered Sep 17 '22 22:09

PSkocik