Почему printf ведет себя так при использовании struct? - PullRequest
0 голосов
/ 28 марта 2019

Я пишу код при использовании структур.Я новичок в структурах, поэтому я тренируюсь, чтобы привыкнуть к нему.В любом случае, пытаясь использовать printf для переменной типа string, которая является переменной типа struct, printf печатает только '@' вместо всей строки.

...

void signPlayers(struct player players[9], int playersC) // players is an array declared on main and playersC is the size of it.
{
    for (int i = 0; i < playersC; i++)
    {
        char tname[22];
        printf("Please enter the name of the player #%d: \n",i+1);
        int res = scanf(" %s",&tname);
        while(res != 1)
        {
            printf("Please enter a valid name for player #%d: \n",i+1);
            res = scanf(" %s",&tname);
        }
        players[i].name = tname;
        printf("Player #%d signed as %s!\n\n",players[i].id,players[i].name); // this printf actually works fine
    }
}

int checkForWinner(struct player players[], int playersC)
{
    for (int i = 0; i < playersC; i++)
    {
        if (players[i].pos == 10)
            return 0;
        printf("%s\n",players[i].name); // prints "@" instead of the name
    }
    return 1;
}

...

, поэтому, если я ввел имя Joey,Сначала printf фактически печатает «Joey», затем, когда я вызываю функцию checkForWinner (она вызывается после функции signPlayers), printf теперь печатает только «@» вместо полного имени снова.Что может быть не так?

1 Ответ

1 голос
/ 28 марта 2019

Ваш код пытается получить доступ к памяти стека после возврата функции.Когда вы делаете это, вы получаете только то, что осталось после вызова функции, или, возможно, некоторую часть нового стекового фрейма, принадлежащего другой функции.В любом случае, это неопределенное поведение.

Есть несколько проблем с присваиванием players[i].name = tname; в функции signPlayers:

  1. Это назначение адреса локального массивапеременная tname.Эта переменная выходит из области видимости на каждой итерации цикла.Это означает, что доступ к этому указателю после цикла будет неопределенным поведением.

  2. Даже если tname был перемещен из области действия цикла в область действия функции, он все равно выйдет из области действиякак только функция вернется.В любом случае доступ к нему после вызова функции является неопределенным поведением.

  3. Вполне вероятно, что адрес tname не меняется от одной итерации цикла к следующей.Таким образом, это означает, что .name член каждого игрока в массиве players, вероятно, будет идентичным (а также недействительным).

Существует много способов исправить это.Вот три способа:

  1. Сделайте копию tname каждый раз через цикл, используя strdup(tname), и назначьте ее players[i].name:

    players[i].name = strdup(tname);
    

    Функция strdup выделяет память, поэтому, если вы используете этот подход, вам нужно будет помнить free .name члена каждого игрока, когда вы закончите.

  2. Динамически выделяйте память для каждого players[i].name перед вызовом signPlayers:

    for (i=0; i<playersC; ++i) players[i].name = malloc(22);
    signPlayers(players, playersC);
    // Don't forget to call free() on each .name member after you're done
    

    Внутри signPlayers, затем вы избавитесь от tname в целом и выполните:

    int res = scanf(" %s", players[i].name);
    

    Примечание: здесь не нужно делать 22 * sizeof(char), поскольку стандарт C гарантирует sizeof(char) == 1.Кроме того, я использовал 22, потому что это то, что код OP использует для объявления tname.Тем не менее, следует отметить, что scanf здесь не идеально, поскольку он не дает возможности ограничить число байтов, записываемых в массив, и, следовательно, не может защитить от переполнения буфера.Если вы введете имя длиной более 21 символа (необходимо оставить 1 байт для нулевого терминатора), то вы переполнитесь tname, и ваша программа либо аварийно завершит работу, либо испортит ваши данные без уведомления.

  3. Превратите players[i].name в размерный массив char вместо простого указателя char.Другими словами, статически выделяйте память каждому players[i].name.Преимущество этого состоит в том, что нет необходимости вызывать free, как вам нужно было бы делать с методами 1 и 2. OP не показывал определение struct player, но этого должно быть достаточно для примера:

    struct player {
      char name[22];
      // other stuff
    };
    

    И снова, внутри signPlayers, вы бы scanf прямо в players[i].name вместо использования tname.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...