char ** x - указатель на указатель, который полезен, когда вы хотите изменить существующий указатель вне его области действия (скажем, в вызове функции).
Это важно, потому что C передается при копировании, поэтому, чтобы изменить указатель в другой функции, вы должны передать адрес указателя и использовать указатель на указатель следующим образом:
void modify(char **s)
{
free(*s); // free the old array
*s = malloc(10); // allocate a new array of 10 chars
}
int main()
{
char *s = malloc(5); // s points to an array of 5 chars
modify(&s); // s now points to a new array of 10 chars
free(s);
}
Вы также можете использовать char ** для хранения массива строк. Однако, если вы распределяете все динамически, не забывайте следить за длиной массива строк, чтобы вы могли проходить по каждому элементу и освобождать его.
Что касается вашего последнего вопроса, char * str; просто объявляет указатель без выделенной ему памяти, тогда как char str [10]; выделяет массив из 10 символов в локальном стеке. Локальный массив исчезнет, как только он выйдет из области видимости, поэтому, если вы хотите вернуть строку из функции, вы хотите использовать указатель с динамически выделенной (malloc'd) памятью.
Кроме того, char * str = "Некоторая строковая константа"; также указатель на строковую константу. Строковые константы хранятся в разделе глобальных данных вашей скомпилированной программы и не могут быть изменены. Вам не нужно выделять память для них, потому что они скомпилированы / жестко запрограммированы в вашей программе, поэтому они уже занимают память.