65

I'm trying to understand how to solve this trivial problem in C, in the cleanest/safest way. Here's my example:

#include <stdio.h>

int main(int argc, char *argv[])
{
    typedef struct
    {
        char name[20];
        char surname[20];
        int unsigned age;
    } person;

    // Here I can pass strings as values...how does it work?
    person p = {"John", "Doe", 30};

    printf("Name: %s; Age: %d\n", p.name, p.age);

    // This works as expected...
    p.age = 25;

    //...but the same approach doesn't work with a string
    p.name = "Jane";

    printf("Name: %s; Age: %d\n", p.name, p.age);

    return 1;
}

The compiler's error is:

main.c: In function ‘main’: main.c:18: error: incompatible types when assigning to type ‘char[20]’ from type ‘char *’

I understand that C (not C++) doesn't have a String type and instead uses arrays of chars, so another way to do this was to alter the example struct to hold pointers of chars:

#include <stdio.h>

int main(int argc, char *argv[])
{
    typedef struct
    {
        char *name;
        char *surname;
        int unsigned age;
    } person;

    person p = {"John", "Doe", 30};

    printf("Name: %s; Age: %d\n", p.name, p.age);

    p.age = 25;

    p.name = "Jane";

    printf("Name: %s; Age: %d\n", p.name, p.age);

    return 1;
}

This works as expected, but I wonder if there a better way to do this.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Gianluca Bargelli
  • 1,780
  • 2
  • 17
  • 23

6 Answers6

65

The first example doesn't work because you can't assign values to arrays - arrays work (sort of) like const pointers in this respect. What you can do though is copy a new value into the array:

strcpy(p.name, "Jane");

Char arrays are fine to use if you know the maximum size of the string in advance, e.g. in the first example you are 100% sure that the name will fit into 19 characters (not 20 because one character is always needed to store the terminating zero value).

Conversely, pointers are better if you don't know the possible maximum size of your string, and/or you want to optimize your memory usage, e.g. avoid reserving 512 characters for the name "John". However, with pointers you need to dynamically allocate the buffer they point to, and free it when not needed anymore, to avoid memory leaks.

Update: example of dynamically allocated buffers (using the struct definition in your 2nd example):

char* firstName = "Johnnie";
char* surname = "B. Goode";
person p;

p.name = malloc(strlen(firstName) + 1);
p.surname = malloc(strlen(surname) + 1);

p.age = 25;
strcpy(p.name, firstName);
strcpy(p.surname, surname);

printf("Name: %s; Age: %d\n",p.name,p.age);

free(p.surname);
free(p.name);
Péter Török
  • 114,404
  • 31
  • 268
  • 329
11

Think of strings as abstract objects, and char arrays as containers. The string can be any size but the container must be at least 1 more than the string length (to hold the null terminator).

C has very little syntactical support for strings. There are no string operators (only char-array and char-pointer operators). You can't assign strings.

But you can call functions to help achieve what you want.

The strncpy() function could be used here. For maximum safety I suggest following this pattern:

strncpy(p.name, "Jane", 19);
p.name[19] = '\0'; //add null terminator just in case

Also have a look at the strncat() and memcpy() functions.

Artelius
  • 48,337
  • 13
  • 89
  • 105
  • 1
    For now the best answer but also Péter's one is good (showing how to do it with pointers) so i'm waiting a little more to see if more people can add more tips/suggestions on the subject. – Gianluca Bargelli Jun 28 '10 at 10:38
  • Few years late. If if we do something like `char *s; s = "foobar"; ` which, unlike arrays, actually works. I mean I can't do `char s[9]; s = "foobar"`. Aren't we trying to assign string value to `s` in both cases? – mayankkaizen May 07 '20 at 09:42
6

The two structs are different. When you initialize the first struct, about 40 bytes of memory are allocated. When you initialize the second struct, about 10 bytesof memory are allocated. (Actual amount is architecture dependent)

You can use the string literals (string constants) to initalize character arrays. This is why

person p = {"John", "Doe",30};

works in the first example.

You cannot assign (in the conventional sense) a string in C.

The string literals you have ("John") are loaded into memory when your code executes. When you initialize an array with one of these literals, then the string is copied into a new memory location. In your second example, you are merely copying the pointer to (location of) the string literal. Doing something like:

char* string = "Hello";
*string = 'C'

might cause compile or runtime errors (I am not sure.) It is a bad idea because you are modifying the literal string "Hello" which, for example on a microcontroler, could be located in read-only memory.

Gus
  • 4,375
  • 5
  • 31
  • 50
  • You are correct, the assignment you wrote cause a segfault because you are trying to alter a pointer value, but referring to my example things goes like char *string = "Hello"; string = "C"; (note there's no pointer assignment on the last statement) which works as expected. – Gianluca Bargelli Jun 28 '10 at 10:07
  • 2
    I would like to mention that "altering a pointer value" does not necessarily cause a segfault. The reason that the snippet in my original post causes a segfault is because you are trying to modify memory which is located in a restricted address space. – Gus Jun 28 '10 at 10:13
0

The first struct is a character array [] and the second struct is a pointer * to the character string (size 8 bytes for a 64-bit machine). According to Stephen Kochan's book "Programming in C", the only time that C lets you assign a constant string is when defining and initializing a char array as in

char name[20] = { "John Doe" };

not even with

char name[20];
name = { "John Doe" };

In the case of char *name; name is a character pointer, not an array. When you did

p.name = "Jane";

it points to another string object.

person p = { .surname = "Doe", .name = "Johnny", .age = 30 };
printf("Ptr. value:\tp.name: 0x%p;\tp.surname: 0x%p\n", p.name, p.surname);
p.name = "Spy, watch out!";
printf("Ptr. value:\tp.name: 0x%p;\tp.surname: 0x%p\n", p.name, p.surname);

output:

Ptr. value:     p.name: 0x00007FF726F7B16C;     p.surname: 0x00007FF726F7B174
Ptr. value:     p.name: 0x00007FF726F7ACE8;     p.surname: 0x00007FF726F7B174

However, in the character array [] case, after you do

strcpy(p.name, "Jane");

to change its content, the address of the buffer p.name[] never changes.

An interesting parallel between C and Python is that Python's String is immutable and is similar to C's string pointer where string literals are read-only. Python's List is mutable and is similar to C's character array.

>>> name = "John"
>>> print(hex(id(name)))
0x261654235f0
>>> name = "Jane"
>>> print(hex(id(name)))
0x261654237b0
>>> type(name)
<class 'str'>
>>> name[1] = 'o'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment
>>> name = list(name)
>>> type(name)
<class 'list'>
>>> name
['J', 'a', 'n', 'e']
>>> name[1] = 'o'
>>> name
['J', 'o', 'n', 'e']
>>> name = ''.join(name)
>>> name
'Jone'
>>> type(name)
<class 'str'>
>>>
Leon Chang
  • 669
  • 8
  • 12
0

In both cases, you are writing:

p.age = 25;
p.name = "Jane";
  • in the first case, p.name is an array, and it's not possible to assign arrays in C
  • in the second case, p.name is a char*, and those can be assigned to string literals, because string literals are arrays of char (arrays are convertible to pointers)

You can use functions such as strcpy, memcpy et al. as shown in other responses, but you can also circumvent this issue by assigning the whole struct.

// compound literal, C99 feature
p = (person) {.age = 25, .name = "Jane", .surname = p.surname};

In practice, it is useful to bundle strings as a char* and size_t in one struct, so this can often be done for individual strings as well.

Jan Schultke
  • 17,446
  • 6
  • 47
  • 96
0

Here is an example of how to implement safe string assignment. If a string is longer than the target array, an assertion fails and the program quits.

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

#define LEN(arr) (sizeof (arr) / sizeof (arr)[0])
#define APAR(arr) (arr), LEN(arr)

void Assign(char target[], int targetLen, const char source[], int sourceLen)
{   
    size_t srcStrLen;
    
    srcStrLen = strnlen(source, sourceLen);
    assert(targetLen > srcStrLen);
    memcpy(target, source, srcStrLen);
    target[srcStrLen] = '\0';
}

int main(void)
{
    typedef struct {
        char name[20];
        char surname[20];
        int unsigned age;
    } person;
    
    person p;
    
    Assign(APAR(p.name), APAR("Jane"));
    Assign(APAR(p.surname), APAR("Anderson"));
    p.age = 25;
    printf("Name: %s %s; Age: %d\n", p.name, p.surname, p.age);
    return 0;
}
August Karlstrom
  • 10,773
  • 7
  • 38
  • 60