0

After executing this code:

char **argv_p;
char *argv[20] = { NULL };

memcpy(&argv_p, &argv, sizeof(argv_p));

argv_p ends up being NULL.

This is because &argv is decomposing into a pointer to the first pointer in the argv array. Using &argv[0] yields identical results (as expected).

Question is, whether there's syntax in C that would allow a char *** to be passed to memcpy, without resorting to something hacky like

char **argv_p;
char *argv[20] = { NULL }, **argv_start = argv;

memcpy(&argv_p, &argv_start, sizeof(argv_p));

Which I have tested and produces the correct result.

Edit: I know you can do straight assignment, but this is for working around const issues. Bonus internet points if anyone knows why execve takes a char * const * instead of a char const ** as its second argument.

Edit Edit: To clarify, the difference between the consts:

char * const * - makes the contents of the array immutable char const ** - makes the contents of the string buffers pointed to by the array immutable.

Const always constifies the thing to the left, unless it appears first (the ANSI C guys need shooting for that) in which case it constifies the thing on the right. Although many people write const char *, it's considered best practice by some, to write char const * because then application of the const is consistent.

You can't cast away a const without receiving a warning from the C compiler. memcpy is the only way to work around this without warnings.

Many older libraries don't correctly mark arguments to their functions as const, and if you have applied const correctly to types in your code, the compiler will emit warnings. That is why using memcpy is occasionally acceptable.

Arran Cudbard-Bell
  • 5,912
  • 2
  • 26
  • 48
  • Are you trying to copy from the address of the first element, or are you trying to copy from the address of the pointer that points to the first element? And are you trying to copy to the pointer to a char, or the pointer to the pointer to a char? – dave234 May 15 '15 at 03:15
  • **Question**: What is the difference between `char *const *` and `char const **`? And why do you think `char const **` makes more sense than the obvious `const char **`? Also, to resolve `const` issues, you need to cast `const` away, which is normally a bad idea, since if the parameter was marked `const` there has to be a reason, so you rather make a copy and a `const` poitner to it. – Iharob Al Asimi May 15 '15 at 03:18
  • @iharob Here's a good answer for the first part of the question. http://stackoverflow.com/questions/1143262/what-is-the-difference-between-const-int-const-int-const-and-int-const – dave234 May 15 '15 at 03:33
  • It might be a typo in your question but `memcpy(&arg_p, &argv, sizeof(argv_p));` does not modify `argv_p`... My guess is that it is `argv_p` everywhere. – francis May 15 '15 at 10:38
  • It was a typo, corrected. – Arran Cudbard-Bell May 15 '15 at 12:37
  • The question is, why does ``execve`` need to modify the contents of the argument buffers?! ``char const * const *`` would seem like the most appropriate application of consts here. – Arran Cudbard-Bell May 15 '15 at 12:38
  • Systems I use have `int execve(const char *path, char *const argv[], char *const envp[]);` – Greg A. Woods May 15 '15 at 19:54

2 Answers2

0

Do not try to work around const by copying a pointer.

Instead it may be necessary to copy the data the pointer points to, and then you can point to the non-const data with a non-const pointer and all will be well with the compiler and optimizer.

Aliasing a pointer to avoid qualifiers is never really acceptable, no matter how it is done, i.e. copying a pointer to a variable with different qualifiers is even not good practice, and in some cases may even cause runtime errors or other undefined behaviour (e.g. in the case where a const qualifier causes the referenced storage to be read-only, and the alias is (ab)used to try to actually modify the storage).

Where possible qualifiers should be removed "upstream" to avoid causing unnecessary clashes with system libraries that may not accept parameters with qualifiers (C of course does not always allow for this possibility).

Where absolutely necessary, and where it can be shown to not cause problems, it is acceptable to use an __UNCONST() macro. If the compiler for the target platform does not offer one then the best solution is to define __UNCONST() as a no-op and to document the potential warning that may result as acceptable (and show why).

Greg A. Woods
  • 2,663
  • 29
  • 26
  • Right, and incur a performance penalty for no real reason. Imagine if the array was on the heap instead of the stack, you'd need to figure out the length of the array, malloc a whole new chunk of memory, then memcpy the contents of the old array across. – Arran Cudbard-Bell May 15 '15 at 19:28
  • No, you incur a performance penalty for a real reason, if you want your code to be portable. Also, do not sweat the small things: *"Premature optimization is the root of all evil in programming."* -- C.A.R. Hoare – Greg A. Woods May 15 '15 at 19:49
  • Also, why can't you make your copy of the pointer `const` as well? – Greg A. Woods May 15 '15 at 19:51
  • BTW, `memcpy()` is perhaps overkill if you really must coerce away a `const` attribute. See if your system offers `__UNCONST()` – Greg A. Woods May 15 '15 at 19:55
  • As far as i'm aware there are no portability issues from using memcpy in this way, so long as the memory that was marked as a const is not modified. Hence my question about the arguments to execve, and querying why only the pointers in the array are const, and not the arguments themselves. __UNCONST looks useful, but that's definitely not portable :) – Arran Cudbard-Bell May 15 '15 at 21:01
  • Premature optimisation is evil, as are spurious mallocs, and memcpying large amounts of data needlessly. Mallocs especially add up fast, which is why libraries like tcmalloc exist, and the memory pools feature of talloc. – Arran Cudbard-Bell May 15 '15 at 21:10
  • Regarding const, it's because another function called before ``execve`` takes a ``char const *argv[]`` to fill with arguments, and the compiler will rightly throw a warning passing that to ``execve``. The code is here: https://github.com/FreeRADIUS/freeradius-server/blob/v3.1.x/src/main/exec.c#L85 – Arran Cudbard-Bell May 15 '15 at 21:13
  • I agree the `memcpy()` is strictly portable, but it is definitely ugly, easily confusing, and arguably wrong because you're aliasing a pointer. For the question as it is asked, I think my answer is really the only correct one for a strict interpretation and for portable code. However for your real problem, perhaps if you posted sufficient code (probably in another new question), then maybe a better suggestion could be offered. – Greg A. Woods May 15 '15 at 21:20
  • `rad_expand_xlat()` -- now there's your problem! Fix that mess first! – Greg A. Woods May 15 '15 at 21:28
  • Not my code and I didn't ask for comments on it. A "you can't do what you've asked" answer may be appropriate if that's the case, so if you want to edit your answer to include such a statement i'd accept it. – Arran Cudbard-Bell May 18 '15 at 18:56
  • It's not that you can't do it -- bytes are bytes, and so long as you copy enough of them in the right order, well, you can do it. However it's not the right way, not the best way, and it is especially not even remotely appropriate for the kind of case you're talking about. Don't suppress a warning by obfuscating the code! In this case it's not even the system library prototype at fault -- it's other code in the very same project! – Greg A. Woods May 19 '15 at 04:45
  • The more or less widely accepted canonical way of avoiding warnings about `const` qualifiers is to use a compiler-supplied `__UNCONST()` macro as I suggested and the portable way to deal with the lack of such a macro is to define it as a no-op when necessary. That will cause the least amount of obfuscation and confusion to future maintainers. However once again it is not the best and proper fix for the code you're referencing. – Greg A. Woods May 19 '15 at 04:47
0

In execve, argv can be declared as char *const argv[] and reading this backwards it is an array of constant strings, but the array is mutable and can be changed to include different constant strings. In old-fashioned Unix systems, the C main function could take 3 arguments, the third argument is the environment array just like execve. In main(), if you modified argv properly (e.g., setting elements to "" until you run into the terminating NULL) you could prevent the ps command from displaying the command line parameters that had been passed to your program. By setting argv[0]="foo", the command would show up as if "foo" were the name of the executable. For example, if your command took a password as a command line parameter, it would be prudent to hide it from ps or maybe /proc/PID/cmdline on newfangled Linux systems. See http://netsplit.com/hiding-arguments-from-ps for how to do the equivalent thing in a newfangled Linux system.

Paul Buis
  • 805
  • 7
  • 7