How to pass a char** to a function that expects a const array of const char pointers
Asked Answered
C

5

5

As shown below, I want to pass a variable defined by char **x to a function as read only.

A reference book shows linear search source code that executes linear search to a variable defined by int *x by passing it to search function as a temporary argument defined by const int a[].

So I hit upon an idea that what if in the case of a string? Then I wrote the code below.

gcc, clang and MSVC Analysis can be found here.

// Linear Search

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int scanf_s(const char *format ,...); // for gcc and clang environment
// int search(const char **a, int n, char *key) {       //※[1]not allowed in c lang specification⇒NG
// int search(char * const *a, int n, char *key) {      //※[2]⇒NG
// int search(const char * const *a, int n, char *key) {    //※[3]warning occured in gcc and clang!!
// int search(char const * const *a, int n, char *key) {//※[4]same as above
// int search(const char * const a[], int n, char *key) {//※[5]same as above
// int search(const char *a[], int n, char *key) {      //※[6]I thought this was ok, but warning occured in gcc and clang!!
int search(char **a, int n, char *key) {            //in conclusion, gcc,clang and MSVC only allowing this!!
    int i = 0;

    for (i = 0; i < n; i++) {
        if (strcmp(a[i], key) == 0)
            return i;
    }
    return -1;

    /* or while style!! the reference book shows this style!!
    while (1) {
        if (i == n)
            return -1;
        if (strcmp(a[i], key) == 0)
            return i;
        i++;
    }
    */
}

int main(void) {
    char **x;
    char *ky;
    int nx;
    int idx;
    int i;

    puts("Linear Search");
    printf("How many Elements??:");
    scanf_s("%d", &nx);
    
    x = malloc(sizeof(char*) * nx);
    if (x == NULL) {
        printf("Pointer to Pointer x malloc failed!!\n");
        exit(EXIT_FAILURE);
    }

    for (i = 0; i < nx; i++) {
        printf("x[%d]:", i);
        x[i] = malloc(sizeof(char) * 35);
        if (x[i] == NULL) {
            printf("Pointer Array x[] malloc failed!!\n", i);
            exit(EXIT_FAILURE);
        }
        scanf_s("%s", x[i], 35);
    }

    printf("Target Value:");
    ky = malloc(sizeof(char) * 35);
    if (x == NULL) {
        printf("target value malloc failed!!\n");
        exit(EXIT_FAILURE);
    }
    // Or
    // ky = calloc(35, sizeof(char));
    scanf_s("%s", ky, 35);

    idx = search(x, nx, ky);

    if (idx == -1)
        puts("no target value.");
    else
        printf("%s is in x[%d]!!\n", ky, idx);

    free(ky);
    for (i = 0; i < nx; i++) {
        free(x[i]);
    }
    free(x);
    system("pause");
    return 0;
}

Code with const makes gcc and clang show warnings though MSVC can compile without any warning as comments(※[1]~[6]) show.

So, how to write code under the idea that search function has to accept a variable defined by char **x as read-only argument?

Chartism answered 21/1 at 3:16 Comment(9)
printf("Pointer Array x[] malloc failed!!\n", i); ... what is your own thought about that? Please explain the details. And what about scanf_s("%s", ky, 35); ?Ciro
thank you for your comments. printf("Pointer Array x[%d] malloc failed!!\n", i);is correct! scanf_s("%s", ky, 35); means same as scanf("%s", ky); for msvc environment. So I added sentence int scanf_s(const char *format ,...); // for gcc and clang environment in the source code on the post. I'm really sorry. I added a link gcc, clang and msvc analysis on the post. here.Chartism
Mr./Mrs. @TedLyngmo I replied the comment. Do you have any idea?Chartism
I'm not sure, but I think this may be a dup of #69119067Buckels
@Buckels thank you for your announcement.:)!! I thought it was dup.But the answer is not clear yet, and the answer to the question has been already considered and rejected as warning in this situation...Chartism
@Buckels that question is about how to pass an array of arrays to a function expecting char **, which is quite different to the question here (how to pass an array of char * to a function expecting something like const char **)Etra
stackoverflow.com/questions/28062095 similar questionEtra
Simply don't use const char* const* and similar deep-const types as function parameters in C. At least until the standards committee adopts the corresponding rule from C++. And if you encounter such a function in a third party library, call it by casting your char **.Magneto
@n.m.couldbeanAI please give me teaching more details. Is my understanding right that generally using const char* const* is not recommendable and in case of use in a third party library we need to cast (const char* const*) at char **? I understand your comment like that.Chartism
E
5

Compilers want the strings in the array to be const char* type. Simply cast your data to const char* const* while calling the function.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>

#define STRING_LENGTH 36

int scanf_s(const char *format ,...);

unsigned int search(const char* const stringArray[], unsigned int stringCount, const char* stringToSearch)
{
    for(unsigned int i = 0; i < stringCount; i++)
    {
        if(strcmp(stringArray[i], stringToSearch) == 0)
        {
            return i;
        }
    }

    return UINT_MAX;
}

int main(void)
{
    printf("Enter the count of strings: ");

    unsigned int strCount;
    scanf_s("%u", &strCount);

    char* *strArray = malloc(sizeof(char*) * strCount);
    if(strArray == NULL)
    {
        printf("Could not get memory for the string array!\n");
        return EXIT_FAILURE;
    }

    for(unsigned int i = 0; i < strCount; i++)
    {
        strArray[i] = calloc(sizeof(char), STRING_LENGTH);
        if(strArray[i] == NULL)
        {
            printf("Could not get memory for the next string!\n");
            return EXIT_FAILURE;
        }

        printf("Enter %uth string (%i characters at most): ", (i + 1), (STRING_LENGTH - 1));
        scanf_s("%s", strArray[i], STRING_LENGTH);
    }

    char* strToSearch = calloc(sizeof(char), STRING_LENGTH);
    if(strToSearch == NULL)
    {
        printf("Could not get memory for the string to be searched!\n");
        return EXIT_FAILURE;
    }

    printf("Enter string to be searched (%i characters at most) : ", (STRING_LENGTH - 1));
    scanf_s("%s", strToSearch, STRING_LENGTH);

    unsigned int result = search((const char* const*)strArray, strCount, strToSearch);
    if(result != UINT_MAX) {
        printf("String found at index: %u\n", result);
    } else {
        printf("String was not found!\n");
    }

    free(strToSearch);
    for(unsigned int i = 0; i < strCount; i++)
    {
        free(strArray[i]);
    }
    free(strArray);

    return EXIT_SUCCESS;
}

I tried this code in the link you provided. Check it here. The output is as follows:

Enter the count of strings: 3
Enter 1th string (35 characters at most): Goodbye
Enter 2th string (35 characters at most): Cruel
Enter 3th string (35 characters at most): World
Enter string to be searched (35 characters at most) : Cruel
String found at index: 1

EDIT: The original question was about passing a char** to a function expecting const char* const a[] but in the answers and after questions, it has somehow evolved to the ultimate way of protecting the data in a function. So, I decided to add a short tutorial on character pointers.

A Short Tutorial on Strings in C (With Pointers)

Usage of char* to Represent A String

We all know that pointers store just memory addresses and not data. If you declare a char* cptr;, it does not yet point to a valid memory address. So, *cptr = 'a'; is invalid and the compiler produces a warning for that.

int main(void)
{
    char* cptr;
    *cptr = 'a'; // gcc -> warning: ‘cptr’ is used uninitialized
    printf("*cptr: %c\n", *cptr);
    return 0;
}

If you try to run the above program, it causes to a Segmentation Fault.

To be able to use a character pointer, you must first point it to a valid memory location with something like this: char* cptr = malloc(sizeof(char) * 6); Or you can assign the address of a valid memory location from another char* containing a char or a string.

int main(void)
{
    char* cptr = malloc(sizeof(char));
    *cptr = 'a';
    printf("*cptr: %c\n", *cptr);
    free(cptr);

    cptr = malloc(sizeof(char) * 6);
    strcpy(cptr, "Hello\0");
    printf("*cptr: %s\n", cptr);
    free(cptr);
    
    return 0;
}

The output is:

*cptr: a
*cptr: Hello

The length of the character array is not important for char* since it always just points to the address of the first character and assumes that the string is terminated with a null character (\0) at the end of a contiguous space.

Usage of const with char*

After explaining the declaration and usage of char*, it is now time to explain the usage of const keyword with character pointers.

Recall that pointers can provide us 3 different values: the addresses of themselves, the addresses they point to and the data at the addresses they point to.

We can't change the addresses of pointers but we can change the other properties of pointers.

In the last program, you saw that *cptr first points to a memory location containing only one character. And after that, it was set to point to another location which can store up to 6 characters (including \0 character). That usage was an example of how the address pointed by a pointer can be changed.

If you don't want a pointer to point to another location you need to use a const keyword before the name of the pointer like this: char* const cptr

int main(void)
{
    char* const cptr = malloc(sizeof(char));
    *cptr = 'a';
    printf("*cptr: %c\n", *cptr);
    free(cptr);

    cptr = malloc(sizeof(char) * 6); // gcc -> error: assignment of read-only variable ‘cptr’
    strcpy(cptr, "Hello\0");
    printf("*cptr: %s\n", cptr);
    free(cptr);
    
    return 0;
}

In the program above, compiler does not allow the assignment of a new address to *cptr in the line cptr = malloc(sizeof(char) * 6); *cptr points to the same location throughout the program execution.

Other than changing the addresses pointed to by pointers, you can change the data at the pointed addresses. This can be done with something like this: strcpy(cptr, "Hello\0");

int main(void)
{
    char* const cptr = malloc(sizeof(char));
    *cptr = 'a';
    printf("*cptr: %c\n", *cptr);
    *cptr = 'b';
    printf("*cptr: %c\n", *cptr);
//  cptr = malloc(sizeof(char)); // gcc -> error: assignment of read-only variable ‘cptr’
    free(cptr);


    char* const sptr = malloc(sizeof(char) * 6);
    strcpy(sptr, "Hello\0");
    printf("*sptr: %s\n", sptr);
    strcpy(sptr, "World\0");
    printf("*sptr: %s\n", sptr);
//  sptr = malloc(sizeof(char) * 6); // gcc -> error: assignment of read-only variable ‘sptr’
    free(sptr);
    
    return 0;
}

The output you will get is:

*cptr: a
*cptr: b
*sptr: Hello
*sptr: World

In the program above, although you can't change the addresses pointed to by the pointers *cptr and *sptr, you can change the data at the pointed addresses.

What if you want to protect the data at the address pointed by *cptr and not the address pointed to? In the program below, const char* cptr can be set to point to both *newCharPtr1 and *newCharPtr2 one after the other. However, *cptr cannot change the data at the addresses pointed by them.

int main(void)
{
    const char* cptr;
    char *newCharPtr1 = malloc(sizeof(char));
    *newCharPtr1 = 'a';
    printf("*newCharPtr1: %c\n", *newCharPtr1);
    cptr = newCharPtr1;
    printf("*cptr: %c\n", *cptr);

    char *newCharPtr2 = malloc(sizeof(char));
    *newCharPtr2 = 'b';
    printf("*newCharPtr2: %c\n", *newCharPtr2);
    cptr = newCharPtr2;
    printf("*cptr: %c\n", *cptr);

    *cptr = 'c'; // gcc -> error: assignment of read-only location ‘*cptr’

    free(newCharPtr1);
    free(newCharPtr2);
    
    return 0;
}

Also, in code written by others you may see const char* and char const* are used interchangeably. Those are exactly the same.

The strange thing is that sometimes C Compilers can be fooled as in the program below.

int main(void)
{
    const char* sptr;
    char* newStringPtr1 = malloc(sizeof(char) * 6);
    strcpy(newStringPtr1, "Hello\0");
    printf("*newStringPtr1: %s\n", newStringPtr1);
    sptr = newStringPtr1;
    printf("*sptr: %s\n", sptr);

    char* newStringPtr2 = malloc(sizeof(char) * 6);
    strcpy(newStringPtr2, "World\0");
    printf("*newStringPtr2: %s\n", newStringPtr2);
    sptr = newStringPtr2;
    printf("*sptr: %s\n", sptr);

    // gcc -> warning: passing argument 1 of ‘strcpy’ discards ‘const’ qualifier from pointer target type
    strcpy(sptr, "Cruel\0");
    // gcc -> /usr/include/string.h:141:39: note: expected ‘char * restrict’ but argument is of type ‘const char *’
    printf("*newStringPtr2: %s\n", newStringPtr2);

    putchar('\n');
    const char* const sptr2 = newStringPtr2;
    printf("*newStringPtr2: %s\n", newStringPtr2);
    printf("*sptr2: %s\n", sptr2);
    // gcc -> warning: passing argument 1 of ‘strcpy’ discards ‘const’ qualifier from pointer target type
    strcpy(sptr2, "What?\0");
    // gcc -> /usr/include/string.h:141:39: note: expected ‘char * restrict’ but argument is of type ‘const char *’
    printf("*newStringPtr2: %s\n", newStringPtr2);
    printf("*sptr2: %s\n", sptr2);

    free(newStringPtr1);
    free(newStringPtr2);
    
    return 0;
}

The output of the above program is:

*newStringPtr1: Hello
*sptr: Hello
*newStringPtr2: World
*sptr: World
*newStringPtr2: Cruel

*newStringPtr2: Cruel
*sptr2: Cruel
*newStringPtr2: What?
*sptr2: What?

Although I declared *sptr to be const char* sptr and *sptr2 to be const char* const sptr2, somehow strcpy() managed to modify data at the addresses pointed by them. This is because strcpy() is using pointer arithmetic and it adds another level of indirection. There is no end to adding another level of indirection. This is a clear example of why you should TREAT WARNINGS AS ERRORS.

To see another example of how you can fool a C Compiler, please check @dbush's answer below.

Representation of String Arrays in C (char** arr / char* arr[])

After reviewing the representation of strings in C, it is now time to review the representation of String Arrays in C.

You declared a string with something like char* str. A string array is declared just appending two square brackets to it: char* str[]. If you want to use pointers, you do it as char* *str which can be read as a char* (str) to another char* (str[0]) which is the first element in a list of char* elements.

The program below shows how you can construct a string array using pointers. Note that you can't use array notation to get memory from the heap. However, after getting memory from the heap with pointer notation, you can use it as an array. This is because arrays do not have their own addresses and they can't point to another address in the heap. You can try and see it for yourself by printing the address of an array and the address of the first element in the array.

#define STRING_LENGTH 6
#define STRING_COUNT 2

int main(void)
{
    char* str1 = calloc(sizeof(char), STRING_LENGTH);
    strcpy(str1, "Hello\0");

    char* str2 = calloc(sizeof(char), STRING_LENGTH);
    strcpy(str2, "World\0");
    
    char** strArr = calloc(sizeof(char*), STRING_COUNT);
    strArr[0] = str1;
    strArr[1] = str2;

    for(unsigned int i = 0; i < STRING_COUNT; i++)
    {
        printf("strArr[%u]: %s\n", i, strArr[i]);
    }

    return 0;
}

The output is:

string[0]: Hello
string[1]: World

Usage of const with char**

Do you remember how you can try to protect the content of a char*? By putting a const before it: const char*.

And what does char** mean? An array of char* elements. Can you make the strings constant in that array? Yes, you can do it by putting a const before the elements: const char* *arr.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define STRING_COUNT 2
#define STRING_LENGTH 6

int main(void)
{
    char* str1 = calloc(sizeof(char), STRING_LENGTH);
    strcpy(str1, "Hello\0");

    char* str2 = calloc(sizeof(char), STRING_LENGTH);
    strcpy(str2, "World\0");

    const char* *strArr = calloc(sizeof(char*), STRING_COUNT);
    strArr[0] = str1;
    strArr[1] = str2;

    for(unsigned int i = 0; i < STRING_COUNT; i++)
    {
        printf("string[%u]: %s\n", i, strArr[i]);
    }

    strArr[0][0] = 'W'; // gcc -> error: assignment of read-only location ‘**strArr’

    for(unsigned int i = 0; i < STRING_COUNT; i++)
    {
       free(strArr[i]);
    }
    free(strArr);

    return 0;
}

What if you want the elements in the array not to change with other char* elements? You put another const keyword before the array name and you get const char* const *arr or const char* const* arr or const char* const arr[]. Those 3 are the same things (while accessing the elements but not while getting memory from the heap, recall what I said earlier about array notation).

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define STRING_COUNT 2
#define STRING_LENGTH 6

int main(void)
{
    char* str1 = calloc(sizeof(char), STRING_LENGTH);
    strcpy(str1, "Hello\0");

    char* str2 = calloc(sizeof(char), STRING_LENGTH);
    strcpy(str2, "World\0");

    const char* const* strArr = calloc(sizeof(char*), STRING_COUNT);
    strArr[0] = str1; // gcc -> error: assignment of read-only location ‘*strArr’
    strArr[1] = str2; // gcc -> error: assignment of read-only location ‘*(strArr + 8)’

    for(unsigned int i = 0; i < STRING_COUNT; i++)
    {
        printf("string[%u]: %s\n", i, strArr[i]);
    }

    for(unsigned int i = 0; i < STRING_COUNT; i++)
    {
       free(strArr[i]);
    }
    free(strArr);

    return 0;
}

The above example shows that you can't use a const char* const* arr after getting the memory because the assignment of a char* to its elements is immediately forbidden.

To be able to initialize a const char* const* arr, you must do something like the following.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define STRING_COUNT 2
#define STRING_LENGTH 6

int main(void)
{
    char* str1 = calloc(sizeof(char), STRING_LENGTH);
    strcpy(str1, "Hello\0");

    char* str2 = calloc(sizeof(char), STRING_LENGTH);
    strcpy(str2, "World\0");

    const char* *strArrTemp = calloc(sizeof(char*), STRING_COUNT);
    strArrTemp[0] = str1;
    strArrTemp[1] = str2;

    const char* const* strArr = strArrTemp;

    for(unsigned int i = 0; i < STRING_COUNT; i++)
    {
        printf("string[%u]: %s\n", i, strArr[i]);
    }

    // strArr[1] = str1; // gcc -> error: assignment of read-only location ‘*(strArr + 8)’

    for(unsigned int i = 0; i < STRING_COUNT; i++)
    {
       free(strArr[i]);
    }
    free(strArr);

    return 0;
}

The output is:

string[0]: Hello
string[1]: World

How to (Try to) Guarantee The Safety of A Pass-by-Reference Argument (char**) In A Function

There are three different places a data can be saved in the memory: read-only memory, stack memory and heap memory. All the previous examples were using heap memory. The next and final example will show all three of them. It's a long program for an example but will make you understand the protection of an argument in a function.

Don't focus on the usability or repetition of same things.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>

#define STRING_COUNT 3
#define STRING_LENGTH 12
#define SEARCH_STRING "Heap\0"

unsigned int search0(char* stringArray[], unsigned int stringCount, const char* const StringToSearch)
{
//  stringArray[0][0] = 'W'; // no warning or error
    strcpy(stringArray[0], "search0\0"); // no warning or error
//  stringArray[0] = calloc(sizeof(char), 10); // no warning or error
//  stringArray = malloc(sizeof(char*) * 10); // no warning or error
    printf("Inside search0(char* stringArray[], ...)\n");
    for(unsigned int i = 0; i < stringCount; i++)
    {
        if(strcmp(stringArray[i], StringToSearch) == 0)
        {
            return i;
        }
    }

    return UINT_MAX;
}

unsigned int search1(char* const stringArray[], unsigned int stringCount, const char* const StringToSearch)
{
//  stringArray[0][0] = 'W'; // no warning or error
    strcpy(stringArray[0], "search1\0"); // no warning or error
//  stringArray[0] = calloc(sizeof(char), 10); // error
//  stringArray = malloc(sizeof(char*) * 10); // no warning or error

    printf("Inside search1(char* const stringArray[], ...)\n");
    for(unsigned int i = 0; i < stringCount; i++)
    {
        if(strcmp(stringArray[i], StringToSearch) == 0)
        {
            return i;
        }
    }

    return UINT_MAX;
}

unsigned int search2(const char* stringArray[], unsigned int stringCount, const char* const StringToSearch)
{
//  stringArray[0][0] = 'W'; // error
    strcpy(stringArray[0], "search2\0"); // warning
//  stringArray[0] = calloc(sizeof(char), 10); // no warning or error
//  stringArray = malloc(sizeof(char*) * 10); // no warning or error

    printf("Inside search2(const char* stringArray[], ...)\n");
    for(unsigned int i = 0; i < stringCount; i++)
    {
        if(strcmp(stringArray[i], StringToSearch) == 0)
        {
            return i;
        }
    }

    return UINT_MAX;
}

unsigned int search3(const char* const stringArray[], unsigned int stringCount, const char* const StringToSearch)
{
//  stringArray[0][0] = 'W'; // error
    strcpy(stringArray[0], "search3\0"); // warning
//  stringArray[0] = calloc(sizeof(char), 10); // error
//  stringArray = malloc(sizeof(char*) * 10); // no warning or error

    printf("Inside search3(const char* const stringArray[], ...)\n");
    for(unsigned int i = 0; i < stringCount; i++)
    {
        if(strcmp(stringArray[i], StringToSearch) == 0)
        {
            return i;
        }
    }

    return UINT_MAX;
}

unsigned int search4(const char* const* stringArray, unsigned int stringCount, const char* const StringToSearch)
{
//  stringArray[0][0] = 'W'; // error
    strcpy(stringArray[0], "search4\0"); // warning
//  stringArray[0] = calloc(sizeof(char), 10); // error
//  stringArray = malloc(sizeof(char*) * 10); // no warning or error

    printf("Inside search4(const char* const* stringArray, ...)\n");
    for(unsigned int i = 0; i < stringCount; i++)
    {
        if(strcmp(stringArray[i], StringToSearch) == 0)
        {
            return i;
        }
    }

    return UINT_MAX;
}

unsigned int search5(const char* const* const stringArray[], unsigned int stringCount, const char* const StringToSearch)
{
//  stringArray[0][0] = 'W'; // error
    strcpy(stringArray[0], "search5\0"); // warning
//  stringArray[0] = calloc(sizeof(char), 10); // error
//  stringArray = malloc(sizeof(char*) * 10); // no warning or error

    printf("Inside search5(const char* const* const stringArray[], ...)\n");
    for(unsigned int i = 0; i < stringCount; i++)
    {
        if(strcmp(stringArray[i], StringToSearch) == 0)
        {
            return i;
        }
    }

    return UINT_MAX;
}

unsigned int search6(const char* const* const stringArray, unsigned int stringCount, const char* const StringToSearch)
{
//  stringArray[0][0] = 'W'; // error
    strcpy(stringArray[0], "search6\0"); // warning
//  stringArray[0] = calloc(sizeof(char), 10); // error
//  stringArray = malloc(sizeof(char*) * 10); // error

    printf("Inside search6(const char* const* const stringArray, ...)\n");
    for(unsigned int i = 0; i < stringCount; i++)
    {
        if(strcmp(stringArray[i], StringToSearch) == 0)
        {
            return i;
        }
    }

    return UINT_MAX;
}

void printStringArray(char* stringArray[], unsigned int stringCount, unsigned int stringPosition)
{
    printf("Inside printStringArray(char* stringArray[], ...)\n");
    for(unsigned int i = 0; i < stringCount; i++)
    {
        printf("stringArray[%u]: %s\n", i, stringArray[i]);
    }

    if(stringPosition != UINT_MAX) {
        printf("%s found at: %u\n\n", SEARCH_STRING, stringPosition);
    } else {
        printf("%s was not found!\n", SEARCH_STRING);
    }
}

// Don't focus on the usability of the program.
// Or repetition of similar things.
int main(int argc, char* argv[])
{
    char** strArray = calloc(sizeof(char*), STRING_COUNT);
    for(unsigned int i = 0; i < STRING_COUNT; i++)
    {
        strArray[i] = calloc(sizeof(char), STRING_LENGTH);
    }

    // They are saved in the heap
    strcpy(strArray[0], "Hello\0");
    strcpy(strArray[1], "Heap\0");
    strcpy(strArray[2], "World\0");

    // They are saved in main function stack
    const char strStack1[15] = "Hello\0";
    const char strStack2[15] = "Stack\0";
    const char strStack3[15] = "World\0";
    const char* const strStackArray[] = { strStack1, strStack2, strStack3 };

    printf("Before search function calls, the strings are like the following!\n");
    putchar('\n');

    printf("strArray[0]: %s\n", strArray[0]);
    printf("strArray[1]: %s\n", strArray[1]);
    printf("strArray[2]: %s\n", strArray[2]);
    putchar('\n');

    printf("strStack[0]: %s\n", strStackArray[0]);
    printf("strStack[1]: %s\n", strStackArray[1]);
    printf("strStack[2]: %s\n", strStackArray[2]);
    putchar('\n');

    // Below is the only way of protecting a string from modification.
    // However, you have to initialize the string in the declaration at compile time.
    // And it gives a Segmentation Fault at runtime if you try to modify it.
    // They are saved in the read-only memory section while loading the program.
    const char* strConstant1 = "Hello\0";
    const char* strConstant2 = "Constant\0";
    const char* strConstant3 = "World\0";
    const char* const strConstantArray[] = { strConstant1, strConstant2, strConstant3 };

    printf("strConstantArray[0]: %s\n", strConstantArray[0]);
    printf("strConstantArray[1]: %s\n", strConstantArray[1]);
    printf("strConstantArray[2]: %s\n", strConstantArray[2]);
    putchar('\n');

    printf("After search function calls, the strings are like the following!\n");
    putchar('\n');

    printf("Heap strings array example!\n");
    putchar('\n');

    unsigned int strPosition;

    // no casting needed
    strPosition = search0(strArray, STRING_COUNT, SEARCH_STRING);
    printStringArray(strArray, STRING_COUNT, strPosition);

    // warning without a cast
    strPosition = search1((char* const*)strArray, STRING_COUNT, SEARCH_STRING);
    printStringArray(strArray, STRING_COUNT, strPosition);

    // warning without a cast
    strPosition = search2((const char**)strArray, STRING_COUNT, SEARCH_STRING);
    printStringArray(strArray, STRING_COUNT, strPosition);

    // warning without a cast
    strPosition = search3((const char* const*)strArray, STRING_COUNT, SEARCH_STRING);
    printStringArray(strArray, STRING_COUNT, strPosition);

    // warning without a cast
    strPosition = search4((const char* const*)strArray, STRING_COUNT, SEARCH_STRING);
    printStringArray(strArray, STRING_COUNT, strPosition);

    // warning without a cast.
    strPosition = search5((const char* const* const*)strArray, STRING_COUNT, SEARCH_STRING);
    printStringArray(strArray, STRING_COUNT, strPosition);

    // warning without a cast. Also, accepts (const char* const*)
    strPosition = search6((const char* const* const)strArray, STRING_COUNT, SEARCH_STRING);
    printStringArray(strArray, STRING_COUNT, strPosition);

    for(unsigned int i = 0; i < STRING_COUNT; i++)
    {
        free(strArray[i]);
    }
    free(strArray);

    printf("Stack strings array example!\n");
    putchar('\n');

    // no casting needed
    strPosition = search6(strStackArray, STRING_COUNT, SEARCH_STRING);

    // warning at compile time without casting but it is not important for our aim here
    // However, notice that "const char* const arr[]" was casted to "char**" without a warning or error
    // This shows that if there is an explicit casting to requested type by the programmer,
    // C Compiler does not take any responsibility.
    printStringArray((char**)strStackArray, STRING_COUNT, strPosition);
    putchar('\n');

    printf("Constant strings array example!\n");
    putchar('\n');

    // No warning or error at compile time
    // But Segmentation Fault at runtime in all cases due to strcpy() call in the function bodies.

    // warning without casting
//  search1((char* const*)strConstantArray, STRING_COUNT, SEARCH_STRING);

    // warning without casting
//  search2((const char**)strConstantArray, STRING_COUNT, SEARCH_STRING);

    // no warning or error
//  search3(strConstantArray, STRING_COUNT, SEARCH_STRING);

    // no warning or error
//  search4(strConstantArray, STRING_COUNT, SEARCH_STRING);

    // warning without casting
//  search5((const char* const* const*)strConstantArray, STRING_COUNT, SEARCH_STRING);

    // no warning or error
    search6(strConstantArray, STRING_COUNT, SEARCH_STRING);

    return EXIT_SUCCESS;
}

The output is:

Before search function calls, the strings are like the following!

strArray[0]: Hello
strArray[1]: Heap
strArray[2]: World

strStack[0]: Hello
strStack[1]: Stack
strStack[2]: World

strConstantArray[0]: Hello
strConstantArray[1]: Constant
strConstantArray[2]: World

After search function calls, the strings are like the following!

Heap strings array example!

Inside search0(char* stringArray[], ...)
Inside printStringArray(char* stringArray[], ...)
stringArray[0]: search0
stringArray[1]: Heap
stringArray[2]: World
Heap found at: 1

Inside search1(char* const stringArray[], ...)
Inside printStringArray(char* stringArray[], ...)
stringArray[0]: search1
stringArray[1]: Heap
stringArray[2]: World
Heap found at: 1

Inside search2(const char* stringArray[], ...)
Inside printStringArray(char* stringArray[], ...)
stringArray[0]: search2
stringArray[1]: Heap
stringArray[2]: World
Heap found at: 1

Inside search3(const char* const stringArray[], ...)
Inside printStringArray(char* stringArray[], ...)
stringArray[0]: search3
stringArray[1]: Heap
stringArray[2]: World
Heap found at: 1

Inside search4(const char* const* stringArray, ...)
Inside printStringArray(char* stringArray[], ...)
stringArray[0]: search4
stringArray[1]: Heap
stringArray[2]: World
Heap found at: 1

Inside search5(const char* const* const stringArray[], ...)
Inside printStringArray(char* stringArray[], ...)
stringArray[0]: search5
stringArray[1]: Heap
stringArray[2]: World
Heap found at: 1

Inside search6(const char* const* const stringArray, ...)
Inside printStringArray(char* stringArray[], ...)
stringArray[0]: search6
stringArray[1]: Heap
stringArray[2]: World
Heap found at: 1

Stack strings array example!

Inside search6(const char* const* const stringArray, ...)
Inside printStringArray(char* stringArray[], ...)
stringArray[0]: search6
stringArray[1]: Stack
stringArray[2]: World
Heap was not found!

Constant strings array example!

Segmentation fault

The above program clearly shows that no matter how you declare the function, there is still a way to modify a char** argument in the function unless its elements are in read-only memory. In this example it is done by calling strcpy() on a string in the string array.

Then How Should You Declare Your Function That Expects And Will Not Modify A String Array?

Let's look at search6() again.

unsigned int search6(const char* const* const stringArray, unsigned int stringCount, const char* const StringToSearch)
{
//  stringArray[0][0] = 'W'; // error
    strcpy(stringArray[0], "search6\0"); // warning
//  stringArray[0] = calloc(sizeof(char), 10); // error
//  stringArray = malloc(sizeof(char*) * 10); // error

    printf("Inside search6(const char* const* const stringArray, ...)\n");
    for(unsigned int i = 0; i < stringCount; i++)
    {
        if(strcmp(stringArray[i], StringToSearch) == 0)
        {
            return i;
        }
    }

    return UINT_MAX;
}

Note that you can change 3 things in a char**. The contents of its elements, the addresses of its elements and the address of the array it points to. If you want to guarantee the safety of a char** argument in a function, you have to protect all of them together. And, you can do it by only accepting the array as const char* const* const arr.

You may say: but it can't protect the contents of the elements in the array, why should I use it? If you're developing the function for your own application, it actually doesn't matter whether you use char** arr or const char* const* const arr because you know the aim of the function while developing it and can modify it anytime if there is a problem. However, if you are developing a library for others, you should use it to express the intention of the function to the user clearly for any level.

Note: I did write something like this earlier in the answer: const char* const arr[] is the same as const char* const* const arr.

I already knew it wasn't correct but I wanted to promote the usage of const char* const* const arr over const char* const arr[]. I shall now explain my intention clearly.

If you are using something like const char* const arr[] in the prototype of your function, you are trying to tell the user that you won't modify the content of the array or the contents of its elements. But what about the address it points to? You probably won't modify it neither but you're not telling it.

There may be some cases you may want to modify it. And how will you differentiate those cases? You should tell the user everything clearly. So, although THE UNDERSTANDING OF THE COMPILER of const char* const arr[] and const char* const* const arr are different, YOUR AIM is probably the same.

And, this concludes my answer. :)

Eden answered 21/1 at 11:13 Comment(23)
Oh my gosh!! Thank you so much!! Mr./Mrs. @Eden !! Btw, is it no problem that casting "const char * const *" before actual argument x that declared as char ** x??? I've been asking for such as this solution which shows no warnings under the 3 all compilers(gcc, clang, msvc), though!! I would like to know how you do on this point!! thx!Chartism
Thanks again for your recommendation of editing!! @Eden This time, I tried hard desperately find the answer!! Thank you so much.I want you to tell me this one last thing... In the first place, is it no problem that casting "const char * const *" before actual argument x that declared as char ** x?Chartism
No, it is not a problem cause it is not destructive and you're sending the casted version to the function. You won't going to use it in main.Eden
By the way, I am not sure if you should pass STRING_LENGTH or (STRING_LENGTH -1) to scanf_s. I've never used that function. :)Eden
I see, I think I've got it. Casting "const char * const *" is OK under the specification of C language as I understand it. @Eden Thank you so much for kindly answering the question I commented.I really struggled with this matter, I have an article on that evidence here. You may read it by using translator.I'll comment about this stackoverflow article later, because 2 persons joined the discussion as teachers. Thank you so much. I acknowledge the matter of using STRING_LENGTH in the scanf_s's argument. I'll keep that in mind!Chartism
Yeah, I've made this answered right now!! I really appreciate it. excuse me, but I thought there was comments about next sentence.printf("Enter %uth string (%i characters at most): ", (i + 1), (STRING_LENGTH - 1)); But they disappeared!! Was that solved??Chartism
As I showed the artilce I have above comment, I illustrated some understanding about const location and that meanings like you noted EDIT on your answer. here I would be happy if you take a look at it!! Thanks again! :DChartism
Alright, I researched about "%u". It's very informative. I am really grateful for your help this time. Oh I see you've read it. Thanks a lot!! I wish you continued success in the future. Best wishes!! ;DChartism
You do know that sizeof (char) is identical to 1, right?Mathison
@chqrlie It is not useless. It prevents the modification of the array itself. Without the second const, one can replace the string containing "Goodbye" with another string containing "Hello" in the search() function. :)Eden
@ukll: you are correct, this array syntax for argument definitions is so confusing. const char * const *stringArray would better describe the actual argument received.Rapeseed
@Rapeseed I think It is a matter of taste. Also, I wanted to keep the logic as similar as to the original code in the question. If I was writing code to use in an application, I would change some things. For example, I would not use heap memory for string to be searched. And, I would use fgets() instead of scanf().Eden
@ukll: I agree. I misread the code and though you were qualifying the pointer, which some local style rules encourage (not mine :). I should work on my jetlag...Rapeseed
@Eden After thinking carefully, let me ask a question one more!! Which is better for accepting char** x as the read-only argument in this case? Set the actual argument as (const char **) x and set the dummy argument as (const char * a[], ...).OR set the actual argument as (const char * const *)x and set the dummy argument as (const char * const a[], ...)??? Incidentally, Copilot AI said both are not allowed under the C language specification. :< I definitely need teaching from you...Chartism
Your original question was how to pass a char** to a function that expects const char* const a[] The question you just asked is about protecting the data passed to a function and requires a long answer. But the short answer is there is no way of protecting the data in the function for sure no matter how you cast it before passing to the function because of the definition of how pointers work. I personally prefer using const char* const* const arr in the function prototype. It provides one more level of protection. I will try to extend my answer with such cases when I have time to do it.Eden
@Eden I sincerely thank you for your politely replying my comment again!! Because it leads to deeper thoughts, I think it is worthy of hearing your opinions, too. Thank you very much again!! And also I am looking forward to reading your updates!! ;DChartism
@Eden Now I am sarting to follow your answer post!! I didn't know that it could be followed not to miss updates!:▷ I am very grateful!Chartism
@Chartism It is now finally completed. I hope you and other readers will find it useful cause it took a lot of time to find the examples, explain things and edit the format. :)Eden
I think I corrected all the mistakes in the answer this time. Writing a long article without making mistakes is very difficult.Eden
@Eden I cannot help saluting to your accomplishment!! Let this brief note be an expression of my thanks!! Many thanks!!Chartism
@Eden Oh, my gosh...I've read summary of your posts and I'm worried about whether I understand it enough, I overwhelmed with emotion. Especially in the last, thank you for understanding my intention and reflecting it in the explanation. I did not intend to prefer faultfinding in other comments, just tried to match the explanations. I appreciate your deep consideration.:▷!!Chartism
@Chartism I wish you continued success in the future too. :)Eden
@Eden it's impressively honor here! I sincerely thank you very much! :▷Chartism
T
3

It is not allowed to assign a char ** to a const char **. While it is allowed to assign a pointer of type X to a const pointer of type X, that doesn't extent to multiple pointer levels, only the first.

The reason for this is because allowing such a conversion could result in a const object getting modified. For example:

#include <stdio.h>

int main()
{
    const char c = 1;
    const char *p1 = &c;
    char *p2;
    char **pp1 = &p2;
    const char **pp2 = pp1;    // invalid char ** to const char ** conversion
    *pp2 = p1;                 // p2 now points to c (no warning!)
    printf("p2=%p, &c=%p\n", p2, &c);  // verifies the above
    *p2 = 2;                   // modifying a const object (no warning!), UB
    printf("%d\n", c);

    return 0;
}

Output:

[dbush@db-centos7 ~]$ !gcc
gcc -std=c99 -g -Wall -Wextra -o x1 x1.c
x1.c: In function ‘main’:
x1.c:9:24: warning: initialization from incompatible pointer type [enabled by default]
     const char **pp2 = pp1;    // invalid char ** to const char ** conversion
                        ^
[dbush@db-centos7 ~]$ ./x1
p2=0x7ffee5f87687, &c=0x7ffee5f87687
2

The above can't happen if pp2 is declared as const char * const * pp2, as the *pp2 = p1; line would cause an error. This is where it appears that MSVC does some additional checking that gcc and clang are not.

To achieve what you want, as mentioned in the other answer, you'll need to declare your parameter with both pointers being const:

int search(const char* const *a, int n, char *key) {

And cast the given parameter when you call the function:

idx = search((const char* const *)x, nx, ky);

On a side note, function parameters with array type are automatically adjusted to pointer type. Also, const char and char const mean the same thing.

So these mean the same thing:

int search(const char* const *a, int n, char *key) { //※[3]
int search(char const * const *a, int n, char *key) {//※[4]
int search(const char* const a[], int n, char* key) {//※[5]

And these mean the same thing:

int search(const char **a, int n, char *key) {       //※[1]
int search(const char* a[], int n, char* key) {      //※[6]
Thant answered 21/1 at 15:21 Comment(4)
@Eden The line *pp2 = p1; does this. From the prior line, pp2 contains the address of p2.Thant
Thank you for your posting the answer!! @Thant Excuse me, but...the writer of top of the answer, Mr./Mrs.ukll is saying "const char* const arr[] is the same as const char* const* const arr". How do you think about it??? Please let me know your thought!! Thanks!:)Chartism
@Chartism Making the parameter itself const (as opposed to what it points to) doesn't affect what you pass to it since parameters are passed by value, but it does affect the parameter itself. It means, in this case, you can't assign to arr.Thant
Thank you for your response and answering my question by the comment!! That really saves me. Incidentally, Mr./Mrs. ukll whose answer has been accepted by me kindly now corrected it in "const char* const arr[] is the same as const char* const* arr." from "const char* const arr[] is the same as const char* const* const arr". Please inspect it!! Best wishes!!Chartism
D
1

Though, OP has already accepted one of the answer, I am posting my answer to provide more details around the role played by the const qualifier in the function parameter which is resulted in incompatible pointer type warning.

Argument passing behaves like assignment1).
Let's start with basic, first understand the following:

    char x = 'a';           
    const char * pc = &x;   // perfectly valid   [1]
    char * const cp = &x;   // perfectly valid   [2]

From C#202x Standard [6.5.16.1 Simple assignment (Constraints)]
[emphasis added]

the left operand has atomic, qualified, or unqualified pointer type, and (considering the type the left operand would have after lvalue conversion) both operands are pointers to qualified or unqualified versions of compatible types, and the type pointed to by the left has all the qualifiers of the type pointed to by the right;

In [1], both left and right of assignment has type char and the type pointed by left has all qualifiers of type pointed by right and an additional qualifier const. Same with [2], except the additional qualifier is added to pointer itself, making it constant (read only). Note that, additional qualifier is allowed only for the type that pointer is directly pointing to (i.e. first level of indirection) or to the pointer itself. So, [1] and [2] are perfectly valid statements.

Now coming to the warning message that compiler is throwing in your case. You are passing x, whose type is char **, as first argument to search() function and the type of first parameter a of search() function is const char ** (picked from your commented code - ※[1]). Basically, this is happening:

                a = x
               /     \
  (type of a) /       \ (type of x)
  const char **       char **

This

    const char **

is pointer to pointer to const char type i.e. pointer to pointer to a qualified type (qualified type is at 2nd level of indirection).

and this

    char **

is pointer to pointer to char type i.e. pointer to pointer to an unqualified type.

Do you see difference?

No!

Read it like this:

    const char **   -->   pointer to this
                                       |
                                       v
                                pointer to a qualified type (const char *)

and

          char **   -->   pointer to this
                                       |
                                       v
                                pointer to an unqualified type (char *)

Got the difference... Hence, the compiler is throwing the incompatible pointer type warning.

Compiler wouldn't have thrown warning message if the type of parameter a of search() function would be char * const * a because, then, the qualifier is added at first level of indirection i.e. to the type which a is pointing to directly and not to the type which is pointed by pointer whose address a is holding (i.e. a is pointing to), like in one of the initial example statement - const char * pc = &x;, here, const is added to type (char) pointed by pc directly.

That said, for a type (say char):

    char a = 'z';
    const char * p1 = &a;                    //   *p1 == a
    const char * const * p2 = &p1;           //  **p2 == a
    const char * const * const * p3 = &p2;   // ***p3 == a

    // In above pointer definition statements, qualifier added at 
    // first level of indirection

A C language compiler comply with language standards will not through any warning on any of the above statements, whereas in following statements:

    char a = 'z';
    char               * p1 = &a;   // [1]
    char       *       * p2 = &p1;  // [2]
    char       * const * p3 = p2;   // [3] this is good
    char const * const * p4 = p3;   // [4] WARNING : incompatible pointer type

compiler will throw warning on statement [4] because const qualifier added at second level of indirection. To fix it do this -
Use explicit cast if you have qualifier mismatches at other than the first level of indirection i.e. other than direct pointer.

Based on above explanation, shown below - how to call the commented function prototypes:

  1. These are same:
    int search(const char **a, int n, char *key)
    int search(const char* a[], int n, char* key)

Use explicit cast as the qualifier mismatches at other than the first level of indirection. You can call it like this:

search ((const char **)x, nx, ky);
  1. int search(char* const *a, int n, char *key)

Qualifier is added to the type directly pointed by pointer a, so explicit cast is not needed. You can call it like this:

search (x, nx, ky);
  1. These are same:
    int search(const char* const *a, int n, char *key)
    int search(char const * const *a, int n, char *key)
    int search(const char* const a[], int n, char* key)

Use explicit cast as the qualifier mismatches at other than the first level of indirection. You can call it like this:

search ((const char * const *)x, nx, ky);

1). From C#202x [6.5.2.2 Function calls] [emphasis added]

2 If the expression that denotes the called function has a type that includes a prototype, the number of arguments shall agree with the number of parameters. Each argument shall have a type such that its value may be assigned to an object with the unqualified version of the type of its corresponding parameter.

Divine answered 21/1 at 23:51 Comment(4)
Thank you for your posting the answer!! @h-s Excuse me, but...the writer of top of the answer, Mr./Mrs.ukll is saying "const char* const arr[] is the same as const char* const* const arr". How do you think about it??? Please let me know your thought!! Thanks!:)Chartism
@Chartism No they are not same but the former type can be assigned to the later type.Divine
No, they are not. I will correct it. :)Eden
Now it seems to be corrected!! Thank you for your [email protected]. Best wishes!!Chartism
E
1

The function could be:

int search(char const * const *a, int n, char *key) 

indicating that the characters will not change, and nor will any of the pointers in the list be changed to point somewhere else.

But to call it you will have to write:

search( (char const * const *)a, ...

with an ugly cast. This is due to a design flaw in the language ; there is no reason why search (a, should not be allowed, but the designers did not think of that at the time. I have heard it is fixed in C23.

For further detail see: Pass a two dimensional array to a function of constant parameter (which is basically your same question with int instead of char).

Etra answered 22/1 at 2:55 Comment(3)
It can't protect the data too. If you call strcpy() on one of the strings in the array. Compiler just gives a warning but still modifies it.Eden
@Eden pay attention to compiler diagnostics! Many popular compilers allow ill-formed code with a "warning" , by defaultEtra
@Etra Thank you very much!! Now I began to understand the contents of conversations exchanged between both of you!! Best wishes!!Chartism
C
0

In a form detonated by contacts by the all respondents, I want to post my understanding about pointer to pointer especially about char** with illustration for reference.

malloc

pointer

enter image description here

pointer to pointer

enter image description here

notation with char** under the depth of direction

pointer

enter image description here

pointer to pointer

enter image description here

source code


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int scanf_s(const char *format ,...); // measures for gcc and clang.
int main(void) {
    char** x;
    int nx;
    int i;

    printf("How many elements?:");
    scanf_s("%d", &nx);

    x = malloc(sizeof(char*) * nx);
    if (x == NULL) {
        printf("Failed malloc for char pointer to pointer.\n");
        exit(EXIT_FAILURE);
    }

    for (i = 0; i < nx; i++) {
        printf("x[%d]:", i);
        x[i] = malloc(sizeof(char) * 35);
        if (x[i] == NULL) {
            printf("Failed malloc for pointer or x[%d] which pointer to pointer points to.\n", i);
            exit(EXIT_FAILURE);
        }
        scanf_s("%s", x[i], 35);
    }

    printf("■■■■■■■■■about x[0]■■■■■■■■■\n");
    printf("▼▼▼▼▼▼the address pointer to pointer points to.▼▼▼▼▼▼\n");
    printf("&(**x) %%p : %p\n", &(**x));
    printf("x %%p : %p\n", x);
    printf("&x[0] %%p : %p\n", &x[0]);
    printf("▼▼▼▼▼▼Output with aligned notation.▼▼▼▼▼▼\n");
    printf("x[0] %%s : %s\n", x[0]);
    printf("x[0] %%p : %p\n", x[0]);
    printf("*x[0] %%c : %c\n", *x[0]);
    //printf("*x[0] %%p : %p\n", *x[0]);
    printf("*x %%s : %s\n", *x);
    printf("*x %%p : %p\n", *x);
    printf("**x %%c : %c\n", **x);
    //printf("**x %%p : %p\n", **x);
    printf("&x %%p : %p\n", &x);
    printf("&(*x[0]) %%s : %s\n", &(*x[0]));
    printf("&(*x[0]) %%p : %p\n", &(*x[0]));
    printf("&(**x) %%s : %s\n", &(**x));
    printf("&(*x) %%p : %p\n", &(*x));

    printf("■■■■■■■■■about x[1]■■■■■■■■■\n");
    printf("▼▼▼▼▼▼the address pointer to pointer points to.▼▼▼▼▼▼\n");
    printf("&(*(x + 1)) %%p : %p\n", &(*(x + 1)));
    printf("x + 1 %%p : %p\n", x + 1);
    printf("&x[1] %%p : %p\n", &x[1]);
    printf("▼▼▼▼▼▼Output with aligned notation.▼▼▼▼▼▼\n");
    printf("x[1] %%s : %s\n", x[1]);
    printf("x[1] %%p : %p\n", x[1]);
    printf("*x[1] %%c : %c\n", *x[1]);
    //printf("*x[1] %%p : %p\n", *x[1]);
    printf("*(x + 1) %%s : %s\n", *(x + 1));
    printf("*(x + 1) %%p : %p\n", *(x + 1));
    printf("**(x + 1) %%c : %c\n", **(x + 1));
    //printf("**(x + 1) %%p : %p\n", **(x + 1));
    //printf("&(x + 1) %%p : %p\n", &(x + 1));
    printf("&(*x[1]) %%s : %s\n", &(*x[1]));
    printf("&(*x[1]) %%p : %p\n", &(*x[1]));
    printf("&(**(x + 1)) %%s : %s\n", &(**(x + 1)));
    printf("&(**(x + 1)) %%p : %p\n", &(**(x + 1)));

    printf("■■■■■■■■■about x[2]■■■■■■■■■\n");
    printf("▼▼▼▼▼▼the address pointer to pointer points to.▼▼▼▼▼▼\n");
    printf("&(*(x + 2)) %%p : %p\n", &(*(x + 2)));
    printf("x + 2 %%p : %p\n", x + 2);
    printf("&x[2] %%p : %p\n", &x[2]);
    printf("▼▼▼▼▼▼Output with aligned notation.▼▼▼▼▼▼\n");
    printf("x[2] %%s : %s\n", x[2]);
    printf("x[2] %%p : %p\n", x[2]);
    printf("*x[2] %%c : %c\n", *x[2]);
    //printf("*x[2] %%p : %p\n", *x[2]);
    printf("*(x + 2) %%s : %s\n", *(x + 2));
    printf("*(x + 2) %%p : %p\n", *(x + 2));
    printf("**(x + 2) %%c : %c\n", **(x + 2));
    //printf("**(x + 2) %%p : %p\n", **(x + 2));
    //printf("&(x + 2) %%p : %p\n", &(x + 2));
    printf("&(*x[2]) %%s : %s\n", &(*x[2]));
    printf("&(*x[2]) %%p : %p\n", &(*x[2]));
    printf("&(**(x + 2)) %%s : %s\n", &(**(x + 2)));
    printf("&(**(x + 2)) %%p : %p\n", &(**(x + 2)));


    for (i = 0; i < nx; i++) {
        free(x[i]);
    }
    free(x);
    system("pause");
    return 0;
}

location of const and its affected area

enter image description here

Also, this theme has been treated by here, sorry in Japanese.

I thank all the participants.

Chartism answered 27/1 at 7:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.