我在使用结构的同时编写代码。我是结构的新手,所以我正在练习以适应它。无论如何,当尝试在字符串类型的变量(结构类型的变量)上使用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现在仅打印“ @”,而不是整个名称。 。 怎么了?
答案 0 :(得分:1)
函数返回后,您的代码正在尝试访问堆栈内存。当您执行此操作时,您将得到在函数调用之后剩下的所有垃圾,或者可能是属于不同函数的新堆栈帧的某些部分。无论哪种方式,都是未定义的行为。
函数players[i].name = tname;
中的赋值signPlayers
有几个问题:
它正在分配局部数组变量tname
的地址。在循环的每次迭代中,此变量都超出范围。这意味着在循环之后访问该指针将是未定义的行为。
即使将tname
从循环范围内移到了函数范围内,只要函数返回,它仍然会超出范围。无论哪种方式,在函数调用后访问它都是未定义的行为。
tname
的地址可能不会在循环的一次迭代到下一轮迭代之间发生变化。因此,这意味着.name
数组中每个玩家的players
成员都可能是相同的(并且是无效的)。
有很多方法可以解决此问题。这是三种方式:
每次使用tname
循环复制strdup(tname)
的副本,并将其分配给players[i].name
:
players[i].name = strdup(tname);
strdup
函数会分配内存,因此,如果使用这种方法,则需要记住完成后free
每个玩家的.name
成员。
在调用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
,并且程序将崩溃或以无提示方式破坏数据。
将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
。