Home > Blockchain >  How to pass a formatted string to a struct in C?
How to pass a formatted string to a struct in C?

Time:10-14

Referencing a similar question at Splitting a string into elements of a struct.

In a part of my project, I have been entering into signal: segmentation fault (core dumped). My task is to extract a formatted string from a file (which is a line of a file), and convert it into a struct. For example, I have already extracted a string "t,a,1990" and I would like to use sscanf to generate a struct s where s.title is "t" and so on. I noticed that in the struct given, title and artist are both pointers. I am new to the language and I am wondering why passing a pointer (s.title and s.artist) to a pointer is invalid here.

How can I solve it without changing the struct def?

Here is my code:

#include  <stdio.h>

typedef struct {
  char* title;
  char* artist;
  int year;
} song;

int main(void) {

    song s;

    // read from string

    char str[]= "t,a,1990";
    char* strptr = str;

    sscanf(strptr, "%s,%s,%d", s.title, s.artist, &(s.year));
    printf("title = %s, artist = %s, year= %d\n", s.title, s.artist, s.year);

    return 0;
}

Thank you!

CodePudding user response:

Each variable represents a specific amount of memory. If you try to use more memory than you've allocated (reserved), segmentation faults happen.

In this case, you're 'reading a string to memory', without having allocated any memory to actually hold that memory. The pointer you are supplying does not actually point to anything.

As such, either:

  • Allocate sufficient memory dynamically (and don't forget to clean up)
  • Reserve sufficient memory during compile time.

For the latter case, your struct would look like this:

typedef struct {
  char title[20];
  char artist[20];
  int year;
} song;

This struct reserves 20 characters for your strings. As long as your strings are shorter than that, you're fine. If it gets longer, you'll run into the same problem as before.

Furthermore, the braces in &(s.year) are unnecessary. (&s.year is perfectly fine).

If you want to do it 'without changing the structure definition', you will need to work with dynamic memory allocation.

The key functions in C for this purpose are malloc and free (which require stdlib.h). As such, your code would become:

char str[]= "t,a,y";
char* strptr = str;

s.title = malloc(20);
s.artist = malloc(20);

sscanf(strptr, "%s,%s,%d", s.title, s.artist, &s.year);
printf("title = %s, artist = %s, year= %d\n", s.title, s.artist, s.year);

free(s.title);
free(s.artist);

Failing to call 'free' leads to a memory leak.

CodePudding user response:

When you are making the variable s, title and artist have some garbage address from memory, when you are trying to fill them in the sscanf you are placing those strings to that address in memory which you probably don't own. You will need to set the address to memory space you own, for that you can either make 2 strings before, like this:

int main(void) {
    char title[64];
    char artist[64];
    song s;
    
    s.title = title;
    s.artist = artist;
    /*...*/

but this isn't really a good looking code, for this purpose there is so called dynamic allocation... you can dynamically allocate memory at runtime with malloc function, that takes a size in bytes and returns address (reference) of the allocated memory:

int main(void) {
    song s;
    
    s.title = malloc(sizeof(char) * 64);
    //it's a good practice to check if system was even able to give us memory
    if(s.title == NULL)
        exit(-1);

    /*...*/

    //it's a good practice to free the memory when we are done with it
    free(s.title);
    free(s.artist);

But best way would be to allocate the whole structure at once:

int main(void) {
    song* s;
    
    s = malloc(sizeof(song)   sizeof(char) * 128);
    if(!s)
        exit(-1);
    //since we are dealing with a pointer (reference) to the struct instead the struct itself, we need to use `->` operator to access the members...
    s->title = (char*)(s   1);
    s->artist = s->title   64;
    /*...*/
    sscanf("%s %s %d", s->title, s->artist, &s->year);
    /*...*/
    free(s);

Note that the sizes of those strings are limited so I suggest using "cs" instead of "%s"... If you are using GCC compiler and you don't care about portability, you don't have to do any of this (well, except freeing the memory in the end), you can allocate the memory directly with the sscanf like this:

sscanf("%ms %ms %d", s.title, s.artist, &s.year);

But with bigger projects I'd use some memory managment library, since malloc might have an impact on performance... Also to read comma separated values use "%[^,]" or better %[0-9a-zA-Z ]. This is my go-to site for scanf documentation.

  •  Tags:  
  • c
  • Related