1

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.

3
  • Possibly related: stackoverflow.com/questions/24821800/… Commented Sep 9, 2015 at 16:30
  • did you try to const_cast, when calling function, or reinterpret_cast it? Commented Sep 9, 2015 at 16:32
  • Submit a patch to GS maintainers. Commented Sep 9, 2015 at 16:39

7 Answers 7

2

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)
Sign up to request clarification or add additional context in comments.

3 Comments

I didn't know that argv[argc] should be NULL. I fixed my answer accordingly.
@KubaOber I didn't specify this in the question and it's not explicitly mentioned in the API docs. But it does say 'The arguments are the same as the "C" main function', which I guess implies argv[argc] should be a null pointer.
As much as I like some of the other answers, this seems like the most sensible minimal modification.
1

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.

2 Comments

You should add a notice, that strdup() leaves the caller to care about freeing memory after use.
char* gs_argv0[nargs]; is not standard C++ when nargs is not a constant expression.
1

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.

Comments

1

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
  };

Comments

1

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

3 Comments

Thanks for this comprehensive solution to the problem. What's the reason for the member class derived from std::vector<char*>? I would have used a std::vector<char*> member of Args and freed the memory in Args::~Args.
@StephenPowell The container itself should be a resource handle and must free the resources. str_vector is such a container. It's not Args job to manage the memory of such a container. That's proper C++ design. Moreover, that's the only way to exception safety. If Args constructor would throw, its destructor will never run, but the m_data's will. Generally speaking, if you have to manage a resource manually in C++, outside of a container, you're probably doing something very wrong.
As written, this requires C++14, no? (Args(Args && o) = default;, etc.) But it's easy enough to implement the same idea without any fancy modern stuff.
0

Try to const_cast it:

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

Maybe it will help with fixing warning.

Comments

0

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.