0

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 :(

5
  • 6
    Arrays decay to pointers to their first element, However this decay isn't recursive. So an array of arrays decays to a pointer to arrays and not pointer to pointers. In your case strings will decay to char (*)[2]. Commented Sep 9, 2021 at 12:55
  • As for where to place the const, you have to remember that it's the data that the pointer is pointing to that can be const, which in your case will be the arrays of characters. So to add const it would be char (const *)[2]. Commented Sep 9, 2021 at 12:58
  • You can either do const char *strings[] = { "a", "b", "c" }; to make it work with void f (const char **strings); or char strings[][2] = { "a", "b", "c" }; with void f (char (*strings)[2]);. Commented Sep 9, 2021 at 12:59
  • Or you can make the pointer constant, in which case it would be char (* const)[2], which is what I guess you really want? Commented Sep 9, 2021 at 13:01
  • @Someprogrammerdude: char (const *)[2] is not a proper type name or declaration. char (* const)[2] would be a const pointer to an array of 2 const char. Commented Sep 9, 2021 at 13:39

3 Answers 3

2

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.

Sign up to request clarification or add additional context in comments.

Comments

1

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

Comments

1

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

array of arrays vs. array of pointers

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.