views:

178

answers:

6

I need to hold an array of C strings. Now I know C strings are just an array of chars so essentially what I want is a 2d array of chars. The strings I'm trying to store will also never exceed 6 characters. My plan is to initialize a char array with 50 "string slots" and then if I hit 50 strings reallocate the array's memory to double it's capacity. I've tried something simple like:

int main() {
    char strings[50][6];
    strings[0] = "test";
    printf("The string is: %s", strings[0]);
    return(0);
}

But, when I go to compile it I get the following error:

test.c: In function ‘main’: test.c:3: error: incompatible types when assigning to type ‘char[6]’ from type ‘char *’ test.c:4: warning: incompatible implicit declaration of built-in function ‘printf’

Can anyone point in me in the right direction?

+5  A: 

strncpy(strings[0], "test", 6); unless your C library has strlcpy(). However if you are going to need to vary the size of the storage, you're better off using a char ** with malloc(), realloc() and free().

Graham Lee
+1 for the malloc suggestion as you can't realloc statically declared char arrays very easily.
Michael Dorgan
`strncpy(strings[0], "testTest", 6);` is going to leave you with unterminated string, so actually the safest way to do this is: `snprintf(strings[0], sizeof(strings[0]), "%s", "whatever string you wish");`
che
Careful, if string is 6 characters, then strncpy will not NULL terminate the string.
Casey
Or alternatively, `strings[0][0] = '\0'; strncat(strings[0], "test", 6 - 1);` (`strncat` *does* always nul-terminate the destination).
caf
+2  A: 

One can't assign arrays directly in that way. In your current case, you would need to do something like...

strcpy (strings[0], "test");

It would be more idiomatic to use an array of pointers, though. Have a look at p111 and onwards of K & R.

Brian Hooper
Sadly you're only allowed to flag answers for being spam or offensive, not for containing a recommendation to use `strcpy()`.
Graham Lee
@Graham Lee: That's what downvoting is for. @Brian Hooper: -1 for recommending strcpy.
Platinum Azure
@Graham Lee...could you expand on that, please? I ask in the spirit of seeking enlightenment
Brian Hooper
@Brian Hooper: See http://www.linuxjournal.com/article/6701, or Google "buffer overflow strcpy" for other interesting articles.
Platinum Azure
I can remember at least 2 seperate times in game developement losing many, many hours to some obscure crash in an unrelated portion of code due to a buffer overflowed strcpy. What's worse is the overflows happen in static RAM where you usually don't have begin/end of allocation debugging tags to catch this.
Michael Dorgan
@Graham Lee, Platinum Azure, I agree, you would be quite correct if the string in question were user input. But it isn't, in this case. One could put `strncpy (string[0], "test", 6)` as suggested above, but that just adds redundancy to the problem, as "test" isn't six characters long; perhaps `strncpy (string[0], "test", strlen ("test"))` might be an improvement. What do you think?
Brian Hooper
@Brain Hooper: You're technically right, but I just think that in general `strncpy` should be used to be absolutely safe. What if you change how the input variable is assigned-- maybe it was hard-coded in development but now you want to read it from a config file?
Platinum Azure
@Brian Hooper: Also, regarding your scenario about using 6 vs `strlen("test")`: As it turns out the former is faster, because `strncpy` will keep copying either until it hits 6 characters OR until the null terminator, whichever comes first. `strlen` needs to calculate the length on the other hand, so you iterate twice. Using `strlen("test")` is no good if you have buffer overflow conditions, because it will still copy all of a potentially malicious variable in those conditions. You need to use the maximum length of your destination as your upper bound, not the length of your source.
Platinum Azure
you know how long the string "test" is, but not the general input. `strcpy()` is broken in the general case (as is `strncpy()`, in a more subtle way - it stops you overflowing the buffer, but doesn't guarantee to terminate it correctly).
Graham Lee
A better article: http://insecure.org/stf/smashstack.html "Smashing the stack for fun and profit"
Platinum Azure
@Platinum Azure; thank you, I shall certainly read that article when I have a moment. It may well be wise to have an eye out for damage caused to programs when literals in the code are changed. I will leave the answer up to avoid losing the discussion.
Brian Hooper
This is nonsense. If you are calling `strcpy` without knowing the length of the source string, then *that* is your problem. I have found very few cases where it is okay to truncate the source string while copying "just to be safe". As a programmer, it is your responsibility to make sure that destination is big enough. And `strncpy` is broken anyway.
Alok
+1  A: 

Use strncpy (if at all possible) or strcpy for your assignment.

Michael Dorgan
By which you mean, use strncpy (where possible), right? :-)
Platinum Azure
But of course :)
Michael Dorgan
-1 for strcpy and strncpy.
che
+1  A: 

First the easy part. You do need to

#include <stdio.h>

to get rid of the incompatible printf warning. This has to do with the way the standard says C works, which is to allow you to make some function that is unlike the standard printf, the implicit declaration of that function with its signature (incorrectly) guessed by the compiler, and the compiler knowing that while you can define a different printf you probably didn't actually mean to.

Ok, now the more complicated part. Arrays in C are a little special. The can evaluate to pointer literals (which can't be assigned to, which is similar to trying to 6 = 4;), or they can evaluate to an entire array, depending on context. Usually they are pointer literals, but in this case strings[0] is seen as an array, which is why you get the error you got rather than one stating that strings[0] was an invalid l-value (left-value, meaning something that can be on the left side of a =). Either way you can't copy a character pointer literal (which is what "test" evaluates to) to an array. When you do this on the line where you declare a string (char array) the compiler treats it differently, though, which can cause some confusion. Anyway, you need to either use strcpy to copy the characters that make up "test" or initialize strings[0] to "test" like this:

char strings[50][6] = { "test" };  // only initializes the first member of the array
nategoose
A: 

The problem you have is in the way that the compiler interprets the statement char strings[50][6];

Instead of what you hoped, a char array with 50 slots for 6 char strings, you got a char array of single chars with dimesions 50x6.

Rather than char strings[50][6];, the way you want to initialise your array is as follows (I only know how to do this on the heap, sorry):

char ** strings[50] = malloc(50 * sizeof(char *));
for(int i = 0; i < 50; ++i)
{
    strings[i] = malloc(50 * 6 * sizeof(char *));
}

Don't forget to clean with frees afterwards.

EDIT: And as said above. Before your main method. Inlcude the line #include <stdio.h>. This is where the printf() function is located.

Ben
A: 

You can't assign array contents using the = operator. First of all, an array object cannot be a target of the assignment operator (Online C Standard, draft n1256, section 6.5.16.1, paragraph 1). strings[0] is an array object of type char [6], so it can't appear on the LHS of the = operator.

Second of all, when an array expression is not an operand of either the sizeof or address-of & operators and is not a string literal being used to initialize the contents of another array, the type of the expression is implicitly converted ("decays") from "N-element array of T" to "pointer to T", and the value of the expression is the address of the first element in the array (section 6.3.2.1, paragraph 3).

The string literal "test" is a 5-element array of char (const char in C++) with static extent (meaning the memory for it is allocated at program startup and held until the program exits). However, when it appears in the expression

strings[0] = "test";

its type is converted from "5-element array of char" to "pointer to char" and its value is the address of the first element, so what you wind up doing is attempting to assign a pointer value to an array object, which is not a compatible type; bad juju, over and above not being able to assign an array object anyway.

If you want to copy the contents of one array to another, then you will need to either assign each array element individually, such as

strings[0][0] = 't';
strings[0][1] = 'e';
strings[0][2] = 's';
strings[0][3] = 't';
strings[0][4] = 0;

or even

size_t len = strlen("test");
size_t i;
for (i = 0; i < sizeof strings[0] - 1 && i < len; i++)
  strings[0][i] = "test"[i]; // yes, you can subscript a string literal
strings[0][i] = 0;

or use a library function like memcpy(), strcpy(), strncpy(), strcat(), sprintf(), etc.:

strcpy(strings[0], "test");

or

strncpy(strings[0], "test", sizeof strings[0] - 1); // -1 to leave room
                                                    // for 0 terminator
                                                    // if necessary  

or

sprintf(strings[0], "%*s", (int) sizeof strings[0] - 1, "test");

Note that you can initialize the array's contents when you declare it, like so:

char foo[] = "test";      // foo is implicitly sized to 5 (+1 for 0 terminator)
int  bar[] = {1,2,3,4,5}; // again, size is implied from initializer

float f[3] = {1.0, 2.0, 3.0}; // Initializer cannot contain more items than 
                              // array is sized for

I see there's a merry war over the use of strcpy() vs. strncpy() in the comments to another answer; my position is to use whichever one is appropriate to the given situation. If you know that your buffers are big enough to handle the largest possible input, use strcpy(). If not, use strncpy(), but be aware that you may have to add the 0 terminator manually.

John Bode