Home > Software design >  Can't scanf into an array of structs
Can't scanf into an array of structs

Time:08-07

I have a struct with a char array in it that I want to hold the names of the teams:

typedef struct
{
    int tpoints;
    int tgames;
    int sgoal;
    int cgoal;
    char nam[];
} team;


int main()
{

    team teams[5];
   
    int i;

    for(i = 0; i < sizeof(teams)/sizeof(teams[0]); i  )
    {
        
        printf("Enter team %d name:\n", i   1);
        scanf("%s", &teams[i].nam);

    }

    
    printf("%s \n", teams[1].nam);

    return 0;


}

I want an array of structs since there will be multiple teams and so want a for loop to input the names of the teams with scanf.

When the program runs, the scanf runs but when I try to print a value from it, there's nothing there except the newline.

if I make a single struct and forgo the for loop, I can input a string and print it just fine but as soon as I make it into an array, I can't write to the structs.

I've tried playing around with clearing a newline and the like but nothing seems to work.

CodePudding user response:

You've created something called a flexible array member which is a bit of strange beast. It is an array declared with [] that is the last field of a struct (and the only place [] can be used with a struct field.) What this does is create a struct with an extra array field that is not really part of the struct. In particular sizeof(T) will not include any size for this member and instead it is intended that the programmer will manually arrange for extra memory to be available after the struct for this member. The only way to do that is with malloc, so structs with flexible array members can only be usefully used by allocating them with malloc. You can't have local or global vars with the type, or arrays of the type usefully. So if you want an array, it needs to be an array of pointers, where each pointer points at malloc'd block with space for the struct and the following flexible array.

You might do something like:

typedef struct
{
    int tpoints;
    int tgames;
    int sgoal;
    int cgoal;
    char nam[];
} team;

int main()
{
    team *teams[5];
    char buffer[128];   
    int i;

    for(i = 0; i < sizeof(teams)/sizeof(teams[0]); i  )
    {
        printf("Enter team %d name:\n", i   1);
        if (!fgets(buffer, sizeof(buffer), stdin))
            break;  // EOF or error on input
        int len = strlen(buffer);
        while (len > 0 && isspace(buffer[len-1]))
            --len;   // strip trailing newline/spaces
        buffer[len] = 0;
        if (!(teams[i] = malloc(sizeof(team)   len   1))
            break;   // out of memory
        strcpy(teams[i]->nam, buffer);
    }
    
    printf("%s \n", teams[1]->nam);
    return 0;
}

CodePudding user response:

Your implementation causes an undefined behavior. Your objects has no size and your scanf function just scans the given string into nowhere.

According to my research on "Flexible Array" usage, you should define your objects as pointers first. After that, you should allocate heap memory to put your objects.

The possible code can be:

#include <stdlib.h>

typedef struct
{
    int tpoints;
    int tgames;
    int sgoal;
    int cgoal;
    char nam[];
} team;


int main()
{

    printf("No data found. Please enter the names of the teams:\n");

    team *teams[5];
    int length_of_name = 20;
    int i;

    for(i = 0; i < sizeof(teams)/sizeof(teams[0]); i  )
    {
        teams[i] = (team *)malloc(sizeof(team) sizeof(char)*length_of_name);
        
        printf("Enter team %d name:\n", i   1);
        scanf("%s", teams[i]->nam); //delete &

        teams[i]->tpoints = 0;
        teams[i]->tgames = 0;
        teams[i]->sgoal = 0;
        teams[i]->cgoal = 0;
    }

    
    printf("%s \n", teams[1]->nam);

    return 0;
}

CodePudding user response:

You need allocate memory at the end of the struct like this:

typedef struct
{
    int tpoints;
    int tgames;
    int sgoal;
    int cgoal;
    char nam[256];
} team;

You are correct from your comment that you are allowed a flexible array at the end, otherwise you would get this:

error: flexible array member 'nam' with type 'char []' is not at the end of struct

but that's only in terms of creating a type, not in terms of actually creating memory for it. C lets you do that in case you need to overlay the type on top of existing memory in order to get byte-access to it. But doesn't allocate memory for it, you have to do that another way, either malloc() or a union or assigning pointers, all of which are a bit fragile. It's much easier and safer to specify the memory directly in the struct.

Be warned! Having a team name greater than memory you allocate creates a buffer overflow opportunity, which is a potential security hole. (https://en.wikipedia.org/wiki/Buffer_overflow)

Consider experimenting with two structs:

typedef struct
{
    int tpoints;
    int tgames;
    int sgoal;
    int cgoal;
    char nam[];
} team;

typedef struct
{
    int tpoints;
    int tgames;
    int sgoal;
    int cgoal;
} team_no_nam;

printf("%lu %lu\n", sizeof(team), sizeof(team_no_nam))

For my machine, I get the same answer for both structs, 16. They take up the same amount of memory, and so when you save the team name, you're overwriting the integers of the next team. Or for the last team, you are writing out into the void and you may or may not get a segfault.

CodePudding user response:

Actually, you can create an array of structs as you intended to above. Your code just needed a couple of small tweaks. Here is your refactored code.

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

typedef struct
{
    int tpoints;
    int tgames;
    int sgoal;
    int cgoal;
    char nam[];
} team;

int main()
{

    team teams[5];

    int i;

    for(i = 0; i < sizeof(teams)/sizeof(team); i  )
    {

        printf("Enter team %d name: ", i   1);
        scanf("%s", teams[i].nam);              /* Needed to drop the ampersand - "teams" is already a reference */

    }

    printf("%s \n", teams[1].nam);

    return 0;
}

I believe the bit that was tripping you up was where you referenced the "teams" array with the ampersand. Like all arrays, the array name is in effect, the pointer to the array. So, dropping the ampersand in that line of code allowed the "scanf" function to work. Following was a quick test of your code.

@Una:~/C_Programs/Console/Teams/bin/Release$ ./Teams 
Enter team 1 name: John
Enter team 2 name: Bill
Enter team 3 name: Lewis
Enter team 4 name: Troy
Enter team 5 name: Ben
Bill 

Give that a try.

  • Related