Reputation: 195
I don't quite understand how to correctly arrange const in order to pass a constant array of arrays to a function, for example, an array of strings:
void f (char **strings);
int main (void)
{
char strings[][2] = { "a", "b", "c" };
f (strings);
}
Could you tell me where to put const
? As I understand it, there should be two const
and one of them in the example above should stand before char
.
There is a high probability of a duplicate, but I could not find a similar question :(
Upvotes: 0
Views: 1075
Reputation: 16017
It is important to understand the difference between an array of arrays (for example, an array of arrays of 2 chars) and an array of pointers. As the wording suggests, their element type is totally different:
Each single element of an array of arrays, like my_arr_of_arrays
below, is, well, an array! Not an address, not a pointer: Each element is a proper array, each of the same size (her: 2), a succession of a fixed number of sub-elements. The data is right there in the array object. After char my_arr_of_arrays[][2] = { "a", "b", "c", "" };
, my_arr_of_arrays
is a succession of characters, grouped in pairs: 'a', '\0', 'b', '\0', 'c', '\0', '\0', '\0'. The picture below illustrates that. Each element in my_arr_of_arrays
has a size of 2 bytes. Each string literal is used to copy the characters in it into the corresponding array elements of my_arr_of_arrays
. The data can be overwritten later, the copy is not const.
Contrast this with an array of pointers, like my_arr_of_ptrs
below! Each element in such an array is, well, a pointer. The data proper is somewhere else! The actual data may have been allocated with malloc, or it is static data like the string literals in my example below. Each element — each address — has a size of 4 byte on a 32 bit architecture, or 8 byte on a 64 bit architecture. Nothing is copied from the string literals: The pointers simply point to the location in the program where the literals themselves are stored. The data is const and cannot be overwritten.
It is confusing that these completely different data structures can be initialized with the same curly-braced initializer list; it is confusing that string literals can serve as a data source for copying characters over into an array, or that their address can be taken and assigned to a pointer: Both char arr[3] = "12":
and char *ptr = "12";
are valid, but for arr
a copy is made and for ptr
the address of the string literal itself is taken.
Your program shows that C permits you to pass an address of an array of 2 chars to a function that expects the address of a pointer; but that is wrong and leads to disaster if the function tries to dereference an "address" which is, in fact, a sequence of characters. C++ forbids this nonsensical conversion.
The following program and image may shed light on the data layout of the two different arrays.
#include <stdio.h>
void f_array_of_arrays(char(* const arr_of_arrays)[2])
{
for (int i = 0; arr_of_arrays[i][0] != 0; i++)
{
printf("string no. %d is ->%s<-\n", i, arr_of_arrays[i]);
}
const char myOtherArr[][2] = { "1", "2", "3", "4", "" };
// arr2d = myOtherArr; // <-- illegal: "const arr2d"!
arr_of_arrays[0][0] = 'x'; // <-- OK: The chars themselves are not const.
}
void f_array_of_pointers(const char** arr_of_ptrs)
{
for (int i = 0; arr_of_ptrs[i][0] != 0; i++)
{
printf("string no. %d is ->%s<-\n", i, arr_of_ptrs[i]);
}
arr_of_ptrs[1] = "87687686";
for (int i = 0; arr_of_ptrs[i][0] != 0; i++)
{
printf("after altering: string no. %d is ->%s<-\n", i, arr_of_ptrs[i]);
}
}
int main()
{
char my_arr_of_arrays[][2] = { "a", "b", "c", "\0" }; // last element has two zero bytes.
const char* my_arr_of_ptrs[] = { "111", "22", "33333", "" }; // "jagged array"
f_array_of_arrays(my_arr_of_arrays);
f_array_of_pointers(my_arr_of_ptrs);
// disaster: function thinks elements are arrays of char but
// they are addresses; does not compile as C++
// f_array_of_arrays(my_arr_of_ptrs);
// disaster: function thinks elements contain addresses and are 4 bytes,
// but they are arbitrary characters and 2 bytes long; does not compile as C++
// f_array_of_pointers(my_arr_of_arrays);
}
Upvotes: 1
Reputation: 213286
First of all, the declaration isn't ideal. char strings[][2]
declares an indeterminate amount of char[2]
arrays, where the amount is determined by the initializers. In most cases it makes more sense to declare an array of pointers instead, since that means that the strings pointed at do not need to have a certain fixed length.
So you could change the declaration to const char* strings[3] = { "a", "b", "c" };
. Unless of course the intention is to only allow strings on 1 character and 2 null terminator, then the original code was correct. And if you need read/writeable strings then we can't use pointer notation either.
You can pass a const char* strings[3]
to a function by declaring that function as
void f (const char* strings[3]);
Just like you declared the array you pass to the function.
Now as it happens, arrays when part of a function declaration "decay" into a pointer to the first element. In this case a pointer to a const char*
item, written as const char**
.
So you could have written void f (const char** strings);
and it would have been equivalent.
Upvotes: 2
Reputation: 116
I think this resource explains it nicely. In general, it depends what "depth" of const guarding you need. If you want function "f" to have read-only access on both the "outer" pointer, the pointer it points to (the "inner" pointer), and the char the "inner" pointer points to, then use
const char *const *const strings
If you want to relax the guards to make this legal:
strings = NULL;
Then use:
const char *const *strings
Relaxing further, to allow for:
strings = NULL;
*strings = NULL;
Use only one "const":
const char **strings
Upvotes: 1