0

I am trying to create a program that contains a structure, say "myStruct", that contains a string array "char* array". Then a function to allocate the memory and then print the output, followed by a main function like below:

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

typedef struct myStruct { char *array; } str1;

void display(str1 *p1) {
  char *array;
  array = (char *)malloc(10 * sizeof(char));
  printf("%s", p1->array);
  free(array);
}

int main(void) {
  str1 s1;
  strcpy(s1.array, "Damn");
  return 0;
}

However I expect "Damn" as output but I am getting nothing in output. However during compiling, shows the following warning:

user@localhost: ~ $ clang struct.c -Weverything
struct.c:7:6: warning: no previous prototype for function 'display' [-Wmissing-prototypes]
void display(str1 *p1) {
     ^
1 warning generated.

That means function is not called. I need some help both syntactically and semantically in this case. I am not sure how to call the function display().

  • In the display function, you allocate and free memory, but you never use it. In `main()`, you don't allocate space for the pointer in `s1` to point to, nor do you make `s1.array` point at anything, so the `strcpy()` is undefined behaviour, and anything can happen (probably, but not necessarily, a crash). – Jonathan Leffler Aug 12 '18 at 06:03
  • Do you understand how to pass a pointer to a local variable to a function? If so, then calling `display()` shouldn't present any problem. However, you have work to do in `main()` before you can call `display()`. Some of that work is more or less in the `display()` function — but it doesn't belong in the `display()` function. – Jonathan Leffler Aug 12 '18 at 06:06
  • Also understand that the address of a struct is the address of its first member. There will be no padding between the beginning of the struct and the first member, There is no such guarantee for any of the remaining members. You will also want to see: [Do I cast the result of malloc?](http://stackoverflow.com/q/605845/995714) – David C. Rankin Aug 12 '18 at 06:10
  • The 'no previous prototype for function 'display'` error message does not mean that the function isn't called (though you're correct; your code did not call the function). It means that there was no prototype for the function in scope before it was defined. There are two different ways to fix that. I normally add `static` to the front of the function definition — `static void display(str1 *p1) { … }`. The alternative is to add an explicit function declaration before defining it: `extern void display(str1 *p1);` where the `extern` is optional — I use it, many don't. _[…continued…]_ – Jonathan Leffler Aug 12 '18 at 07:08
  • _[…continuation…]_ One advantage of declaring the function `static` is that you then get a warning about the function being defined but unused — with an external function declaration, you don't get that warning because the function might be called from another source file. – Jonathan Leffler Aug 12 '18 at 07:09

3 Answers3

2

You didn't call display in main(). To allocate memory to char *array and copy the string, you can design the function like this:

#define DEFAULT_LEN 10
void allocate(str1 *p1, const char * str) {
    char * array;
    if(str) {
        //(char *) is omittable in C (not C++)
        array = malloc(strlen(str) + 1);
        strcpy(array, str);
    }
    else {
        array = malloc(DEFAULT_LEN);
        array[0] = 0;
    }
    p1->array = array;
}

and you can print the text like this:

int main() {
    str1 s1; allocate(&s1, "Damn");
    puts(s1.array);
    deallocate(&s1);
    return 0;
}

You have to free memory:

void deallocate(str1 *p1) {
    free(p1->array);
    p1->array = NULL;
}
paxbun
  • 316
  • 1
  • 2
  • 9
  • Your use of `10` instead of `strlen(str) + 1` becomes problematic if the code ever gets beyond 'Damn' ('Damnation' is still OK, but 'Irritating Commentator' is not). – Jonathan Leffler Aug 12 '18 at 06:12
  • You should not cast malloc - google it - Done it https://stackoverflow.com/questions/605845/do-i-cast-the-result-of-malloc – Ed Heal Aug 12 '18 at 06:12
  • You didn't quite copy what I wrote in my comment, so you've not allocated enough space, still. You forgot about the null at the end of the string; `strlen()` doesn't count it, but `malloc()` needs you to do so. – Jonathan Leffler Aug 12 '18 at 06:25
  • @JonathanLeffler Thanks for the comment again. I didn't notice it. – paxbun Aug 12 '18 at 06:33
  • `free(p1->array);` and `allocate(&s1, "Damn")` part clears my doubt. Also I missed `p1->array` part to point to some data. Thank you :) – Trevis Schiffer Aug 12 '18 at 06:42
  • @JonathanLeffler Isn't that `strlen(str)` alone can handle length? Why the extra +1? – Trevis Schiffer Aug 12 '18 at 06:47
  • Also can you explain `array[0] = 0;` part? – Trevis Schiffer Aug 12 '18 at 06:49
  • @NikolaiLvovich: Strings in C are terminated by a null byte. The length of a string returned by `strlen()` counts the bytes up to but not including the null byte. Consequently, to allow for both the string content and the null terminating byte, you need to request `strlen(str) + 1` bytes of memory. So the answer is "**NO**: `strlen(str)` alone cannot handle the length of the allocation". (The `array[0] = 0;` part sets the first byte of the allocated memory to a null byte (often, that would be written `array[0] = '\0';`). It creates an empty string (length 0) in the allocated space.) – Jonathan Leffler Aug 12 '18 at 06:50
  • Ow, that null part. I understood now. – Trevis Schiffer Aug 12 '18 at 06:53
1

With str1 s1 s1 is allocated on the stack, array inside it is just a pointer, no memory is allocated for its contents.

strcpy is then trying to write to wherever that pointer is pointing, it may be zero it may be random depending on your compiler and its settings.

You need to first allocate a buffer for the string, something like:

str1 s1;
size_t len = strlen("Damn");
s1.array = malloc((len+1) * sizeof(char)); // +1 for the null terminator
strcpy(s1.array, "Damn");
Tom
  • 43,583
  • 4
  • 41
  • 61
  • 1
    You should not cast malloc - google it –https://stackoverflow.com/questions/605845/do-i-cast-the-result-of-malloc done it for you – Ed Heal Aug 12 '18 at 06:13
  • 2
    It's fortunate that `sizeof(char)` is `1` — otherwise, your length calculation would be wrong. – Jonathan Leffler Aug 12 '18 at 06:13
  • @JonathanLeffler: Isn't the `sizeof(char)` always `1`? Apart from some architectures (embedded processors typically>) where minimum memory size of 16-bit, I guess some of TMS320 family microcontrollers from TI would be an example of such? – WedaPashi Aug 12 '18 at 06:16
  • 1
    @WedaPashi: Yes, `sizeof(char) == 1` always, even when `CHAR_BIT > 8`. The problem would occur if the size of the type was not 1 but, say, 8. (The code under discussion was `malloc(len + 1 * sizeof(char))` — it has been fixed since.) The multiplication would be done before the addition, which would leave the code with less memory allocated than was intended. That is now fixed by the parentheses around the addition. Whether it is worth multiplying by `sizeof(char)` is open to debate; there are pros and cons. – Jonathan Leffler Aug 12 '18 at 06:23
  • "With str1 s1 s1 is allocated on the stack, array inside it is just a pointer, no memory is allocated for its contents" - This is where I was wrong. I thought str type creates s1 var. And then s1.array can directly access the array var inside struct :D – Trevis Schiffer Aug 12 '18 at 06:36
  • @JonathanLeffler: Ah, I saw the answer after it was rightly edited. Thanks for explanation :-) – WedaPashi Aug 12 '18 at 06:39
1

Let's see if we can't give you a bit of foundation to build from. When you declare s1, you declare a struct of type struct myStruct which conveniently has a typedef to str1; The declartion:

str1 s1;

creates storage for s1, which has automatic storage duration and it is valid only while s1 remains in scope. The automatic storage for s1 includes storage for one pointer array and nothing else. (ignoring padding for the moment). The one pointer array is completely unintialized, and points to some indeterminate address which you do NOT have the ability to write to. Attempting to write to an uninitialized value invokes Undefined Behavior and the valid operation of your program ceases at that point. (in fact attempting to write to it will likely result in a Segmentation Fault)

How do you create storage for s1.array? Here is where you need to allocate. (before you attempt to copy to s1.array) How much memory is required to store your string? strlen(string) + 1 (the +1 to provide storage for the nul-terminating character)

Knowing that, and having followed the link I left in the comment, you can allocate storage for your string with:

s1.array = malloc (strlen ("Damn") + 1);

For every allocation, you will validate the allocation succeeded before attempting to use the block of memory. If malloc fails (and it does), you attempting to use the invalid block puts you right back where you would have been if you failed to allocate at all -- straying off into Undefined Behavior...

Now you have initialized s1.array to point to a valid block of memory and you have validated the allocation succeeded, you can now copy your string to s1.array.

strcpy (s1.array, "Damn");

Your display function need allocate nothing (you can of course). Your display function need only display the string that is now held in s1.array. As Jonathan Leffler mentioned, you can simply pass a pointer to display(str1 *s) and then output s->array from within. Nothing more needed than:

void display (mystruct_t *s)
{
    printf ("%s\n", s->array);
}

which you will call from main() as:

     display (&s);           /* pass the address of s (a pointer to s) */

In any code you write that dynamically allocates memory, you have 2 responsibilities regarding any block of memory allocated: (1) always preserve a pointer to the starting address for the block of memory so, (2) it can be freed when it is no longer needed.

When you return from display() the use of s1.array is over and it can be freed. Get in the habit now of freeing the memory you allocate and don't simply rely on it being freed when the program ends -- this will pay dividends as your code become more complex. Simply,

    free (s1.array);         /* if you allocate it, you free it when done */

And since s1 has automatic storage, there is nothing to free for the structure itself.

Let's leave you with two examples. The first will declare the structure with automatic storage duration and only allocate for s.array (as was discussed above). The second will declare a pointer to your struct now requiring you to allocate for both the structure and array. (which in turn requires you to free both the array and structure)

Structure with Automatic Storage Duration

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

#define STR "Damn"

typedef struct mystruct { 
    char *array;
} mystruct_t;

void display (mystruct_t *s)
{
    printf ("%s\n", s->array);
}

int main (void) {

    mystruct_t s;       /* struct with automatic storage - for the pointer */

    /* s is a struct, so you use the '.' operator access members */
    s.array = malloc (strlen (STR) + 1);    /* you must allocate for array */
    if (s.array == NULL) {                  /* validate each allocation */
        perror ("malloc-s.array");
        return 1;
    }
    strcpy (s.array, STR);  /* with valid memory pointed to, you can copy */

    display (&s);           /* pass the address of s (a pointer to s) */

    free (s.array);         /* if you allocate it, you free it when done */

    return 0;
}

Declaring a Pointer to struct

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

#define STR "Damn"

typedef struct mystruct { 
    char *array;
} mystruct_t;

void display (mystruct_t *s)
{
    printf ("%s\n", s->array);
}

int main (void) {

    mystruct_t *s;          /* declare a pointer to the struct itself */

    s = malloc (sizeof *s); /* now you must allocate for the struct */
    if (s == NULL) {        /* and you validate every allocation */
        perror ("malloc-s");
        return 1;
    }

    /* s is a pointer to struct, so you use -> to reference member */
    s->array = malloc (strlen (STR) + 1);   /* you must allocate for array */
    if (s->array == NULL) {                 /* validate each allocation */
        perror ("malloc-s->array");
        return 1;
    }
    strcpy (s->array, STR); /* with valid memory pointed to, you can copy */

    display (s);            /* s is a pointer, just pass it to display */

    free (s->array);        /* if you allocate it, you free it when done */
    free (s);               /* don't forget to free the struct */

    return 0;
}

(In both cases the output is simply your string)

Look over both paying careful attention to the use of the '.' or -> operators to dereference the structure and access array. You need to understand when and where to use them both.

Let me know if you have further questions.

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
  • Thank you very much for the explanation. I am taught that an array "decays" into a pointer to its first element, that's why I confused about &s and s. Call by reference vs string array thing. But here you cleared the doubt, s was a variable for the pointer. – Trevis Schiffer Aug 12 '18 at 07:42
  • And the real fun began when I started to take arguments the changed the whole story :D – Trevis Schiffer Aug 12 '18 at 07:49
  • Just remember there is no Call by reference in C. It is all call by value. However, C does allow you to pass an address by value (a pointer) which can simulate call by reference. For arrays, the conversion to pointer on access is defined in [C11 Standard - 6.3.2.1 Lvalues, arrays, and function designators(p3)](http://port70.net/~nsz/c/c11/n1570.html#6.3.2.1p3). It isn't just when an array is used as a parameter. The conversion applies "except when it is the operand of the `sizeof` operator, the `_Alignof` operator, or the unary `&` operator, or is a string literal used to initialize an array" – David C. Rankin Aug 12 '18 at 08:25
  • oh, that is interesting info! – Trevis Schiffer Aug 12 '18 at 08:30
  • It's worth bookmarking the standard. It is where all the final answers for "Can I do that in C?" come from. It's not something you read all at once, its more something your digest slowly. When you see references to a standard section, that's where they come from. It takes a while to get comfortable with how it is written (and there are some areas that are gray instead of black and white), but it is the final word. If it isn't addressed in the standard, then it is *implementation defined* and up to the compiler writer to make a sane choice. – David C. Rankin Aug 12 '18 at 08:35
  • Yeah, that's something that holds up to the compilers, compiler versions etc. – Trevis Schiffer Aug 12 '18 at 08:37
  • And it is also a biggie to recognize when something is *implementation defined*, because there is no guarantee on how it will be implemented. It effects code portability because there is no guarantee the implementation defined aspects will be consistent across compilers. Learning C isn't something you do in a month, or a year and you never actually stop learning it (they standard gets updated). There is no language other than assembly, that actually teaches you how to program. C gives you total control -- but it places the total responsibility -- on you. There are no training wheels `:)` – David C. Rankin Aug 12 '18 at 08:44
  • Wait a min, should I go back and catch up asm a bit? Never touched asm till now. – Trevis Schiffer Aug 12 '18 at 08:53
  • No, C provides a high level interface just above assembly. There is no real need to learn assembly before C. In C, you will learn how to account for every byte of memory, protect all array bounds, etc.. Other language attempt to hide memory management from you and their objects try and prevent you from accessing data outside of an object range. There are no such protections in C. Becoming proficient in C will make you a better programmer in whatever language you end up using -- because you will have learned how to program (otherwise, you are just learning a language) there is a difference. – David C. Rankin Aug 12 '18 at 09:00