为什么在使用struct时printf会表现出这种方式?

时间:2019-03-28 01:46:02

标签: c struct

我在使用结构的同时编写代码。我是结构的新手,所以我正在练习以适应它。无论如何,当尝试在字符串类型的变量(结构类型的变量)上使用printf时,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 个答案:

答案 0 :(得分:1)

函数返回后,您的代码正在尝试访问堆栈内存。当您执行此操作时,您将得到在函数调用之后剩下的所有垃圾,或者可能是属于不同函数的新堆栈帧的某些部分。无论哪种方式,都是未定义的行为。

函数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的定义,但这足以满足示例要求:< / p>

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

    同样,在signPlayers内,您可以直接scanf进入players[i].name,而不使用tname