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. :)
printf("Pointer Array x[] malloc failed!!\n", i);
... what is your own thought about that? Please explain the details. And what aboutscanf_s("%s", ky, 35);
? – Ciroprintf("Pointer Array x[%d] malloc failed!!\n", i);
is correct!scanf_s("%s", ky, 35);
means same asscanf("%s", ky);
for msvc environment. So I added sentenceint 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. – Chartismchar **
, which is quite different to the question here (how to pass an array ofchar *
to a function expecting something likeconst char **
) – Etraconst 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 yourchar **
. – Magnetoconst char* const*
is not recommendable and in case of use in a third party library we need to cast(const char* const*)
atchar **
? I understand your comment like that. – Chartism