Stephen Powell
Stephen Powell

Reputation: 153

Array of C strings, initialized with string literals

The Ghostscript interpreter API has a function

GSDLLEXPORT int GSDLLAPI gsapi_init_with_args(void *instance, int argc, char **argv)

The final argument argv is a pointer to an array of C strings, which are interpreted as command-line arguments. I obviously cannot change the signature of the function gsapi_init_with_args to take a const char ** argument instead.

If I were willing to ignore (or silence) the deprecated conversion from string constant to 'char*' warning, then I would write simply

char *gs_argv[] = {"", "-dNOPAUSE", "-dBATCH", ...};

and pass gs_argv as the final argument. But I would prefer to fix my code so that I am not relying on an external function to behave in the way I expect it to (and effectively treat gs_argv as const char**).

Is there any simple way to declare gs_argv as an array of pointers to (non-const) C strings, and initialize its elements with string literals? (That is, using a similar approach to how I can initialize a single C string: using char c_str[] = "abc".) The best I can think of is to use

const char *gs_argv0[] = {"", "-dNOPAUSE", "-dBATCH", ...};

and then copy the contents, element by element, into gs_argv.


Please note that I understand why the compiler gives this warning (and have read the answers to, among others, this question). I am asking for a solution, rather than an explanation.

Upvotes: 1

Views: 284

Answers (7)

Since this code requires C++11, there's a lower cost C++11 solution in another answer below. I'm leaving this one for posterity.

There are pretty much two choices: ignore it and const_cast, or do the right thing. Since this is modern C++, you're supposed to have nice, RAII classes. Thus, the simplest, safest thing to do is to safely wrap such an array.

// https://github.com/KubaO/stackoverflown/tree/master/questions/args-cstrings-32484688
#include <initializer_list>
#include <type_traits>
#include <cstdlib>
#include <cassert>
#include <vector>

class Args {
   struct str_vector : std::vector<char*> {
      ~str_vector() { for (auto str : *this) free(str); }
   } m_data;
   void append_copy(const char * s) {
      assert(s);
      auto copy = strdup(s);
      if (copy) m_data.push_back(copy); else throw std::bad_alloc();
   }
public:
   Args(std::initializer_list<const char*> l) {
      for (auto str : l) append_copy(str);
      m_data.push_back(nullptr);
   }
   template <std::size_t N>
   Args(const char * const (&l)[N]) {
      for (auto str : l) append_copy(str);
      m_data.push_back(nullptr);
   }
   /// Initializes the arguments with a null-terminated array of strings.
   template<class C, typename = typename std::enable_if<std::is_same<C, char const**>::value>::type>
   Args(C l) {
      while (*l) append_copy(*l++);
      m_data.push_back(nullptr);
   }
   /// Initializes the arguments with an array of strings with given number of elements.
   Args(const char ** l, size_t count) {
      while (count--) append_copy(*l++);
      m_data.push_back(nullptr);
   }
   Args(Args && o) = default;
   Args(const Args &) = delete;
   size_t size() const { return m_data.size() - 1; }
   char ** data() { return m_data.data(); }
   bool operator==(const Args & o) const {
      if (size() != o.size()) return false;
      for (size_t i = 0; i < size(); ++i)
         if (strcmp(m_data[i], o.m_data[i]) != 0) return false;
      return true;
   }
};

Let's see how it works:

#include <iostream>

extern "C" int gsapi_init_with_args(void*, int argc, char** argv) {
   for (int i = 0; i < argc; ++i)
      std::cout << "arg " << i << "=" << argv[i] << std::endl;
   return 0;
}

int main()
{
   Args args1 { "foo", "bar", "baz" };
   const char * args2i[] { "foo", "bar", "baz", nullptr };
   Args args2 { (const char **)args2i };
   const char * args3i[] { "foo", "bar", "baz" };
   Args args3 { args3i };
   const char * const args4i[] { "foo", "bar", "baz" };
   Args args4 { args4i };
   const char * args5i[] { "foo", "bar", "baz" };
   Args args5 { args5i, sizeof(args5i)/sizeof(args5i[0]) };

   assert(args1 == args2);
   assert(args2 == args3);
   assert(args3 == args4);
   assert(args4 == args5);

   gsapi_init_with_args(nullptr, args1.size(), args1.data());
}

Output:

arg 0=foo
arg 1=bar
arg 2=baz

Upvotes: 1

Inspired by n.m.'s C++14 version, here's a C++11 version. The trick is to use an evaluated empty lambda expression to generate a fresh type, so that each instantiation of W__ is unique.

template <typename T, int N> static char * W__(const char (&src)[N], T) {
   static char storage[N];
   strcpy(storage, src);
   return storage;
}

#define W(x) W__(x, []{})

char * argv[] = {
   W("foo"),
   W("bar")
};

The static in front of W__'s return type means that W__ has internal linkage and won't bloat the object file with extra symbols. It has nothing to do with the static in front of storage, as the latter indicates the static storage duration for the local variable. The code below would be perfectly valid, but of course doing the wrong thing and having undefined behavior:

template <typename T, int N> static char * BAD(const char (&src)[N], T) {
   char storage[N];
   strcpy(storage, src);
   return storage;
}

Since a lambda has to be evaluated, you can't simply make its type a template argument:

template<typename> void G();
G<decltype([]{})>(); // doesn't work

Upvotes: 0

n. m. could be an AI
n. m. could be an AI

Reputation: 120031

A C++14 solution.

#define W(x) \
  (([](auto& s)->char* \
   { \
     static char r[sizeof(s)]; \
     strcpy (r, s); \
     return r; \
   })(x))

char* argv[] = 
  { W("--foo=bar", 
    W("baz"),
    nullptr
  };

Upvotes: 1

Neil Kirk
Neil Kirk

Reputation: 21803

If you can guarantee that the function will not modify the non-const parameter, then it is acceptable to use const_cast in this situation.

Upvotes: 1

Colonel Thirty Two
Colonel Thirty Two

Reputation: 26599

Create copies of the string literals using strdup. This is more verbose, but fixes the warning.

char* gs_argv0[NARGS];
gs_argv0[0] = strdup("");
gs_argv0[1] = strdup("-dNOPAUSE");
// ...

Note that you will also need to free the memory allocated by strdup if you want to prevent leaks.

You might also want to add a comment to your code saying why you are doing this, to make it clear for future readers.

Upvotes: 1

R Sahu
R Sahu

Reputation: 206697

You can use:

char arg1[] = "";
char arg2[] = "-dNOPAUSE";
char arg3[] = "-dBATCH";

char* gs_argv0[] = {arg1, arg2, arg3, NULL};
int argc = sizeof(gs_argv0)/sizeof(gs_argv0[0]) - 1;
gsapi_init_with_args(instance, argc, gs_argv0)

Upvotes: 2

DawidPi
DawidPi

Reputation: 2365

Try to const_cast it:

gsapi_init_with_args(instance, argc, const_cast<char**>(argv));

Maybe it will help with fixing warning.

Upvotes: 0

Related Questions