12

Playing with pointers in C is fun (not really).

I have several arrays of strings I want to declare in an easy way, preferably something like:

arrayOfStrings1 = {"word1", "word2", etc. };
arrayOfStrings2 = {"anotherword1", "anotherword2", etc. };
arrayOfStrings3 = etc.
etc.

Something similar to a translation array (but not quite), so I want to be able to swap between these during runtime. For that I want a pointer pointerToArrayOfStrings that I can swap like:

pointerToArrayOfStrings = arrayOfStrings1;
doStuff();
pointerToArrayOfStrings = arrayOfStrings2;
doSomeOtherStuff();

In my naive understanding of arrays of strings and pointers to these, this is what I tried:

// Danish transforms
const unsigned char* da_DK[] = {"b","bb","c","c","cc","d","dd","e","f","ff","g","gg","h","hh","j","j","jj","k","k","kk","l","l","l","l","ll","m","mm","n","n","nn","p","pp","r","r","r","rr","s","s","s","ss","t","t","tt","v","v","vv","æ"};

// British english transforms
const unsigned char* en_GB[] = {"a","a","a","a","a","a","a","a","a","a","a","a","a","age","ai","aj","ay","b","cial","cian","cian","dj","dsj","ea","ee","ege","ei","ei","eigh","eigh","f","f","f","g","g","gs","i","i","i","j","j","k","ks","kw","l","m","n","n","o","r","s","s","sd","sdr","sion","sion","sj","sj","tial","tion","tion","tj","u","u","u","u","w","ye","ye","z"};

    // More languages....

const unsigned char** laguageStrings;

// Assign language
if (streq(language, "da-DK")){
    laguageStrings= da_DK;
}
else if (streq(language, "en-GB")){
    laguageStrings= en_GB;
}
else
         return 0;
}

Language is a char * containing the language "en-GB", "da-DK" etc., streq() is just a home brewed (somewhat faster than strcmp()) string comparison function.

Long story short, depending on compiler this approach may work, report compiler warnings or compile, but give unexpected results.

What would be the correct way to solve this problem?

Sangeeth Saravanaraj
  • 16,027
  • 21
  • 69
  • 98
Woodgnome
  • 2,281
  • 5
  • 28
  • 52
  • 2
    You should end your string arrays with a NULL pointer so that you can traverse them sensibly: `const char * strs[] = { "aa", "bb", NULL };` – Kerrek SB Jan 11 '12 at 17:49
  • Depending on compiler it doesn't work. Linux GNU compiler likes it and works, XCODE doesn't and produces some weird errors. – Woodgnome Jan 11 '12 at 17:50
  • @Woodgnome any specific reason to use `unsigned char` not `char`? – Kos Jan 11 '12 at 17:51
  • @Kos For now I'm avoiding multibyte/wide character support as it's written in plain C, but I want to support as many chars from ISO-8859-1 as possible, hence unsigned chars. – Woodgnome Jan 11 '12 at 17:52
  • @KerrekSB I've updated the text to make it more clear what my question is. I hope you understand now. – Woodgnome Jan 11 '12 at 18:01
  • @Woodgnome, have you considered heeding the **compiler warnings** as being relevant? It's still not clear whether your problem is with the custom `streq` function or with your char pointers. Again, the compiler warnings, and any runtime errors or debugger output would be helpful to determine that. – mctylr Jan 11 '12 at 18:10
  • @Woodgnome, I'll suggest you edit your question to give the shortest self-contained (i.e. **complete**, that is compilable) example of the problem you are experiencing. A minimal trivial example. And include the compiler _warnings_ and/or the runtime errors (or debugger output of the error). – mctylr Jan 11 '12 at 18:15
  • @Woodgnome The question is in-complete and vague. I've tried my level best to answer it. +1 for your question as it demands clear understanding of array of characters (strings) to answer your question. – Sangeeth Saravanaraj Jan 11 '12 at 18:36
  • I deliberately didn't provide compiler warnings/errors because I meant this question as a more theoretical question about proper implementation of pointer to arrays of strings - @SangeethSaravanaraj got it exactly right. Apologies if that has been unclear. – Woodgnome Jan 13 '12 at 11:00
  • @Woodgnome I think what you want is not a pointer to an array of strings, but a pointer to an array of pointers to strings. And it should be possible to declare it with one-liners very similar to what you wrote (no malloc required if hardcoded strings are fine for your use). – cesss Dec 30 '19 at 12:33

2 Answers2

26

There are two way of working with array of characters (strings) in C. They are as follows:

char a[ROW][COL];
char *b[ROW];

Pictorial representation is available as an inline comment in the code.

Based on how you want to represent the array of characters (strings), you can define pointer to that as follows

    char (*ptr1)[COL] = a;
    char **ptr2 = b;

They are fundamentally different types (in a subtle way) and so the pointers to them is also slightly different.

The following example demonstrates the different ways of working with strings in C and I hope it helps you in better understanding of array of characters (strings) in C.

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

#define ROW 5
#define COL 10

int main(void) 
{
    int i, j;
    char a[ROW][COL] = {"string1", "string2", "string3", "string4", "string5"};
    char *b[ROW];

    /*

    a[][]

      0   1   2   3   4   5   6     7    8   9
    +---+---+---+---+---+---+---+------+---+---+
    | s | t | r | i | n | g | 1 | '\0' |   |   |
    +---+---+---+---+---+---+---+------+---+---+
    | s | t | r | i | n | g | 2 | '\0' |   |   |
    +---+---+---+---+---+---+---+------+---+---+
    | s | t | r | i | n | g | 3 | '\0' |   |   |
    +---+---+---+---+---+---+---+------+---+---+
    | s | t | r | i | n | g | 4 | '\0' |   |   |
    +---+---+---+---+---+---+---+------+---+---+
    | s | t | r | i | n | g | 5 | '\0' |   |   |
    +---+---+---+---+---+---+---+------+---+---+

    */  

    /* Now, lets work on b */    
    for (i=0 ; i<5; i++) {
        if ((b[i] = malloc(sizeof(char) * COL)) == NULL) {
            printf("unable to allocate memory \n");
            return -1;
        }
    }

    strcpy(b[0], "string1");
    strcpy(b[1], "string2");
    strcpy(b[2], "string3");
    strcpy(b[3], "string4");
    strcpy(b[4], "string5");

    /*

       b[]              0   1   2   3   4   5   6    7     8   9
    +--------+        +---+---+---+---+---+---+---+------+---+---+
    |      --|------->| s | t | r | i | n | g | 1 | '\0' |   |   |
    +--------+        +---+---+---+---+---+---+---+------+---+---+
    |      --|------->| s | t | r | i | n | g | 2 | '\0' |   |   |
    +--------+        +---+---+---+---+---+---+---+------+---+---+
    |      --|------->| s | t | r | i | n | g | 3 | '\0' |   |   |
    +--------+        +---+---+---+---+---+---+---+------+---+---+
    |      --|------->| s | t | r | i | n | g | 4 | '\0' |   |   |
    +--------+        +---+---+---+---+---+---+---+------+---+---+
    |      --|------->| s | t | r | i | n | g | 5 | '\0' |   |   |
    +--------+        +---+---+---+---+---+---+---+------+---+---+

    */

    char (*ptr1)[COL] = a;
    printf("Contents of first array \n");
    for (i=0; i<ROW; i++)
        printf("%s \n", *ptr1++);


    char **ptr2 = b;
    printf("Contents of second array \n");
    for (i=0; i<ROW; i++)
        printf("%s \n", ptr2[i]);

    /* b should be free'd */
    for (i=0 ; i<5; i++)
        free(b[i]);

    return 0;
}
Sangeeth Saravanaraj
  • 16,027
  • 21
  • 69
  • 98
  • So if I wanted to define my array of strings as a `char *[COLS]` I am forced to `malloc()` and then `strcpy()`? There is no way I can initialize it with a list of string like `{"string1", "string2", ...}`? – Woodgnome Jan 11 '12 at 21:08
  • Sorry, that was supposed to be `char *[ROWS]` – Woodgnome Jan 11 '12 at 21:17
  • 2
    @Woodgnome Yes, you cannot assign a string to a `char[]` or `char *` like how you assign an integer value to an integer variable. For string you need to use string library functions like `strcpy()` – Sangeeth Saravanaraj Jan 12 '12 at 02:41
  • Could you guys give an explanation for -1?! – Sangeeth Saravanaraj Jan 12 '12 at 02:43
  • 1
    @Woodgnome Thanks for choosing my answer. I hope it was helpful to you! .. If you like the answer, please feel free to upvote it! Thanks! :) – Sangeeth Saravanaraj Jan 12 '12 at 19:20
  • @SangeethSaravanarj I upvoted as soon as you answered :P The downvote confuses me as well. With regard to my follow up question, I'll word it differently; I know I can not declare a char array `string[5];` and then later initialize it like `string = "test";`, but I've read that it is possible to initialize a read only string like `char *string = "test";` - if so, wouldn't it be possible to do the same with `char *b[ROWS] = {...}` in you example? Provided I don't need to make any changes to the string of course. – Woodgnome Jan 13 '12 at 11:16
  • 1
    the problem with this case `char *b[rows]= {"first","second" ...` is that if you want to change the string it will lead to unspecified behavior !! That's why it is better to use the `malloc()` function ! – Meninx - メネンックス Jan 15 '15 at 22:05
1

What would be the correct way to solve this problem?

Well, the correct way would be to use a library specifically designed for dealing with multilanguage interfaces - for instance gettext.

Another way, though patchier, would be to use a hash table (also known as "dictionary" or "hash map" or "associative map" in other languages/technologies): Looking for a good hash table implementation in C

It's probably not the answer you were looking for, but you've asked the wrong question to the right problem.

Community
  • 1
  • 1
Flavius
  • 13,566
  • 13
  • 80
  • 126