4

I'm fairly new to C and I'm a little confused as to the correct way to initialise struct variables which are pointers, within a function. Is this style sufficient, or do I need to allocate memory before I assign s->str? Thank you kindly for your replies, apologies if the question is unclear as I am very new to this language.

typedef struct Mystruct{
    const char* str1;
    const char* str2;
}mystruct;

mystruct* mystruct_new(const char* str1, const char* str2){
    mystruct *s = (mystruct*)(malloc(sizeof(mystruct)));
    s->str1 = str1;
    s->str2 = str2;
    return s;
}
Jean-François Fabre
  • 137,073
  • 23
  • 153
  • 219
  • if you want a copy of the strings assign to `strdup(str1)`. We would need to see how this function is called to emit an advice. with string literals your code is fine. – Jean-François Fabre Nov 08 '17 at 20:42
  • 1
    See [this question](https://stackoverflow.com/questions/605845/do-i-cast-the-result-of-malloc) for more information about casting the result of malloc in c – mgarey Nov 08 '17 at 20:45
  • It depends on your intent. Do you want a copy of those strings or do you just want to point to strings that already exist? – Christian Gibbons Nov 08 '17 at 20:47
  • I want a copy of the strings, so I see what you mean yes, I would need a duplicate, otherwise s->str1 would point to garbage once the parameter "str1" is destoyed upon completion of the function? – Unlikely1879 Nov 08 '17 at 20:49
  • 1
    @Unlikely1879 s->str1 would not point to garbage at the end of `mystruct_new(...)`. Instead, it would point to the exact same string as you passed in. So if you call `mystruct_new(inputStr1, inputStr2);`, and then change inputStr1, e.g. `inputStr1[0] = 'b'`, it would also change your mystruct's str1. – AlexPogue Nov 08 '17 at 20:54
  • 1
    `str1` isn't local to the function, so it would not be destroyed at the end of the function, but its scope is unknown. If it was scoped in `main`, for example, it would would survive to the end of the program. But depending on what exactly it is, maybe it would be changed and that change would affect your struct since `s->str1` would be pointing to it rather than having its own copy. – Christian Gibbons Nov 08 '17 at 20:54
  • oh I see. What about if you literally called the function without initialising a char, like this: mystruct_new("abc", "123"); – Unlikely1879 Nov 08 '17 at 20:56
  • @ChristianGibbons `str1` is local to the function; the memory pointed to by `str1` is not local – M.M Nov 08 '17 at 20:57
  • @Unlikely1879 that is OK since literals have global scope. – Jean-François Fabre Nov 08 '17 at 20:57
  • @M.M You are technically correct. The best kind of correct. – Christian Gibbons Nov 08 '17 at 20:58
  • @ChristianGibbons your comment is spot on BTW (apart from this str1 detail, but I got it) – Jean-François Fabre Nov 08 '17 at 21:05

4 Answers4

4

your function is legal and doesn't do anything bad. Nevertheless, you should document it to mention that the strings are not copied, only the pointers are.

So if the passed data has a shorter life than the structure itself, you may meet undefined behaviour. Example:

mystruct*func()
{
   char a[]="foo";
   char b[]="bar";

   return mystruct_new(a,b);
}
mystruct*func2()
{
   char *a="foo";
   char *b="bar";

   return mystruct_new(a,b);
}

int main()
{
    mystruct *s = func();
    printf(s->a); // wrong, memory could be trashed
    mystruct *s2 = func2();
    printf(s2->a); // correct
    mystruct *s3 = mystruct_new("foo","bar");
    printf(s3->a); // also correct, string literals have global scope
}

the above code is undefined behaviour for the first print because s->a points to some memory that is no longer allocated (local to func). The second print is OK because s2->a points to a string literal which has infinite life span.

So maybe your function is more useful like this:

mystruct* mystruct_new(const char* str1, const char* str2){
    mystruct *s = malloc(sizeof(mystruct));
    s->str1 = strdup(str1);
    s->str2 = strdup(str2);
    return s;
}

now the memory is allocated for the strings. Don't forget to free it when discarding the structure, better done in another utility function.

Jean-François Fabre
  • 137,073
  • 23
  • 153
  • 219
3

If the strings being passed in to str and str2 will always be string constants than yes you can get away with doing it this way. My guess however is that this is not the case. So you would be better off making a copy of each string with strdup and assigning those to the struct members:

mystruct* mystruct_new(const char* str1, const char* str2){
    mystruct *s = malloc(sizeof(mystruct));
    s->str1 = strdup(str1);
    s->str2 = strdup(str2);
    return s;
}

Just make sure to free each of those fields before freeing the struct.

Jean-François Fabre
  • 137,073
  • 23
  • 153
  • 219
dbush
  • 205,898
  • 23
  • 218
  • 273
  • That's exactly what I was looking to do, well explained and thank you :) – Unlikely1879 Nov 08 '17 at 20:52
  • Be aware that `strdup` is not a standard library function, and may not be supported everywhere. You may have to use a combination of `malloc` and `strcpy` instead. – John Bode Nov 08 '17 at 20:56
1

Think of it this way: when you allocate memory for the struct, you get the pointer member variables for free. So in essence, when you do this:

mystruct *s = malloc(sizeof(mystruct)); //don't cast result of malloc.

Then you can treat s->str1 in the exact same way as you would any regular char* variable, say

char *str1 = NULL;

If you want it to point to something, then you have to allocate memory for the pointers. Consider this:

mystruct* mystruct_new(const char* str1, const char* str2){
    mystruct *s = malloc(sizeof(mystruct);

    char* someString = getMyString(); //gets some arbitrary string
    char* str1 = NULL;//just for demonstration
    int length = strlen(someString) + 1;

   //for struct members
    s->str1 = malloc(sizeof(char) * length);
    strcpy(s->str1, someString);

   //For regular pointers
    str1 = malloc(sizeof(char) * length);
    strcpy(str1, someString);

    return s;
}

Also note that if you just assign to a pointer by using the = operator instead of allocating memory, then it will only copy the address to the original value. This may or may not be what you want depending on the context. Generally, if you know the memory location will stay in scope and you don't need (or don't mind) to change the original string, then it is preferred to simply assign it. Otherwise, it is advisable to make a copy.

//Makes a copy of the string
s->str1 = malloc(sizeof(char) * length);
strcpy(s->str1, someString);

//copies the address of the original value only!
s->str1 = someString;
Nik
  • 1,780
  • 1
  • 14
  • 23
0

Use strncpy() instead of strcpy(). The latter is subject to buffer overruns.

For example in this code snippet given by another user, use the strncpy() in place of strcpy()

mystruct* mystruct_new(const char* str1, const char* str2){
    mystruct *s = malloc(sizeof(mystruct);

    char* someString = getMyString(); //gets some arbitrary string
    char* str1 = NULL;//just for demonstration
    int length = strlen(someString) + 1;

   //for struct members
   s->str1 = malloc(sizeof(char) * length);
   strcpy(s->str1, someString);

  //For regular pointers
  str1 = malloc(sizeof(char) * length);
  strcpy(str1, someString);   // replace with strncpy(str1, someString, bufsize);  where bufsize is the maximum number of characters in your string + 1 for the terminator '\0'.  

  return s;

}

alaniane
  • 92
  • 1
  • 5
  • 1
    I won't downvote since I see your reputation does not allow you to comment yet, but this kind of answer is better left to comments. You should flesh it out more if you want it to stand as an answer. OP never mentions `strcpy`, so advising the use of `strncpy` over it does not really get him anywhere. – Christian Gibbons Nov 08 '17 at 21:11
  • I agree it should go in the comment section; however, since I can't comment yet and he is new to C, I felt it better to at least alert him to a common problem that we encounter in production code. So, I added the code fragment with a comment on where he could use strncpy(). I hope this is sufficient. – alaniane Nov 08 '17 at 21:26