【问题标题】:strcat() and fgets() in C not workingC中的strcat()和fgets()不起作用
【发布时间】:2014-05-12 14:33:10
【问题描述】:

出于某种原因,我无法让fgets() 和strcat() 的两个实例工作。

这是我的代码:

#include <stdio.h>
#include <string.h>

int main()
{
  char str[150];

  char userName[8]; char password[8];
  printf("Enter username: ");fgets(userName, 9, stdin);
  printf("Enter password: ");scanf("%s", password);

  strcpy(str, "INSERT INTO USERS (ID,USERNAME,PASSWORD,IP) "  \
         "VALUES (1, '");
  strcat(str, userName);
  strcat(str, "', '");
  strcat(str, password);
  strcat(str, "', '120.132.12.1');");

  puts(str);
  return 0;
}

当获取“密码”的字符串时,我不能使用fgets() 或者它会抛出错误“中止陷阱:6”。具体来说,我正在使用:

fgets(password, 9, stdin);

9 个元素,因为我读到您需要在总数中考虑空终止字符。

我不明白这一点,因为 char 数组中的元素与 userName 一样多,我认为获取字符串的代码应该与 char 数组名称几乎相同。

此外,strcat() 在尝试将“str”附加到用户名时似乎不起作用。我的输出看起来像这样:

Enter username: beckah12
Enter password: beckah12
INSERT INTO USERS (ID,USERNAME,PASSWORD,IP) VALUES (1, '', 'beckah12', '120.132.12.1')

为什么会跳过"strcpy(str, userName)"?

更新

当我声明这两个字符串时存在一些内存重叠。当我在两个字符串之间声明一个随机整数时,它保留了两者的输入。如何通过更持久的解决方案修复此内存重叠?

【问题讨论】:

  • fgets() 的第二个参数太大。对于char[8],它应该是8。此外,您应该限制(并验证)scanf:scanf("%7s", password);
  • 可能发生的情况是密码的空终止符存储在用户名的第一个字节中,从而使用户名看起来好像是一个空字符串(实际上,它 is 如果此诊断正确,则为空字符串)。打印出读取密码前后username的值。或者尝试 7 个字符的密码。
  • “不会等待”问题意味着fgets() 在读取换行符之前已经没有空间容纳该行数据;因此,第二个fgets() 读取换行符并停止处理。将数据读入更大的字符串(32、64、80、256、4096 字节),然后验证长度等。请记住,fgets() 在合适的情况下在输入字符串中包含换行符。当它不适合时,它会被留下来进行下一次读取操作。将用户名限制为 8 个字符,这有点古怪——有点 80 年代复古风,不是吗?
  • 输入“beckah12”时,您需要一个 10 或更多的缓冲区,因为 fgets() 将读取 "beckah12\n",即 9 char 和终止 '\0'。
  • 您编辑了您的问题以纠正(部分)您所询问的问题。请不要那样做;它会使已发布的答案无效,并使您的问题对未来的读者毫无用处。我已经回滚了编辑。

标签: c string fgets strcat


【解决方案1】:

对于您的fgets(),您正确地说:

9 个元素,因为我读到您需要在总数中考虑空终止字符。

但是在你的变量声明中你忘记了它:

char userName[8]; char password[8];

将它们设为char ....[9],您的程序不应再中止。

但是,正如@chux 提到的:

如果您阅读完整的文本行,您还必须处理换行符,这会使您的代码类似于:

char userName[10]; char password[10];
...
fgets( userName, sizeof userName, stdin );
if( userName[strlen(userName)-1] == '\n' ) {
    //truncate new line character
    userName[strlen(userName)-1] = '\0';
}

...如果有人键入超过 8 个字符的名称或密码,您仍然会遇到问题。所以我建议:

#define L_USER 8

char userName[256]; char password[256];
...
fgets( userName, sizeof userName, stdin );
if( strlen(userName) > L_USER ) {
    //truncate name if too long
    userName[L_USER] = '\0';
}    
if( userName[strlen(userName)-1] == '\n' ) {
    //truncate new line character
    userName[strlen(userName)-1] = '\0';
}

【讨论】:

  • 需要 10 来处理 fgets() 的输入,例如“beckah12\n”。
【解决方案2】:

这是一个彻底的重写,但它也很有效。这更符合我的用途:

#include <assert.h>
#include <stdio.h>
#include <string.h>

static int prompt_for_data(const char *prompt, const char *what, char *buffer, size_t buflen)
{
    char line[256];
    assert(prompt != 0 && *prompt != '\0');
    assert(what != 0 && *what != '\0');
    assert(buffer != 0);
    assert(buflen > 1 && buflen < sizeof(line));

    printf("%s", prompt);
    if (fgets(line, sizeof(line), stdin) == 0)
    {
        fprintf(stderr, "EOF\n");
        return EOF;
    }
    size_t len = strlen(line);
    if (len == 0)
    {
        fprintf(stderr, "Zero length string? You can't use Control-@ in a %s\n",
                what);
        return EOF;
    }
    if (line[len - 1] != '\n')
    {
        fprintf(stderr, "Line far too long (%zu bytes)\n", len);
        return EOF;
    }
    line[--len] = '\0';
    /* Optionally chop leading and trailing white space - omitted */
    if (len > buflen - 1)
    {
        fprintf(stderr, "Maximum length of %s is %zu (you entered %zu characters)\n",
                what, buflen - 1, len);
        return EOF;
    }
    if (len == 0)
    {
        fprintf(stderr, "You're supposed to enter a value for the %s\n", what);
        return EOF;
    }
    strcpy(buffer, line);
    return 0;
}

int main(void)
{
    char userName[9];
    char password[9];

    if (prompt_for_data("Enter username: ", "username", userName, sizeof(userName)) == 0 &&
        prompt_for_data("Enter password: ", "password", password, sizeof(password)) == 0)
    {
        char str[150];
        snprintf(str, sizeof(str),
                 "INSERT INTO USERS (ID, USERNAME, PASSWORD, IP) VALUES"
                 "(%d, '%s', '%s', '%s');",
                 1, userName, password, "120.132.12.1");
        puts(str);
    }
    return 0;
}

'提示输入值,读取一行,检查其长度,复制到所需变量'的序列封装在prompt_for_data()函数中。然后它被使用了两次(但我可能会将它写成一个函数,即使它只被调用一次)来获取用户名和密码。然后,我不使用strcat(),而是使用snprintf() 创建输出字符串。

您还应该注意,如果您想将用户名和密码的大小从古朴的 8 个字符加上终止 null 更改为 32,唯一更改的代码行是 userName 和 @987654327 的声明@。这是因为我在需要描述尺寸时仔细使用了sizeof(userName) 或sizeof(password)。与其他变量类似;例如,如果我更改 line 变量的大小,fgets() 行不会更改。更改变量大小时,尽量减少必要更改的次数通常是一种很好的做法。

请注意,您仍然存在 SQL 注入问题。如果我输入“O'Reilly”作为用户名,则 SQL 无效。记住Little Bobby Tables!短用户名的一件好事;它使 SQL 注入更加困难。

样品运行

$ ./up
Enter username: beckah12
Enter password: beckah12
INSERT INTO USERS (ID, USERNAME, PASSWORD, IP) VALUES(1, 'beckah12', 'beckah12', '120.132.12.1');
$ ./up
Enter username: absolom12
Maximum length of username is 8 (you entered 9 characters)
$ ./up
Enter username: beckah12
Enter password: absolom12
Maximum length of password is 8 (you entered 9 characters)
$ ./up
Enter username: 
You're supposed to enter a value for the username
$ ./up
Enter username: beckah12
Enter password: 
You're supposed to enter a value for the password
$ ./up
Enter username: EOF
$ ./up
Enter username: beckah12
Enter password: EOF
$ ./up
Enter username: nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn
Line far too long (255 bytes)
$

【讨论】:

  • +1 以获得完整答案。次要:代替size_t len = strlen(line); ... line[len - 1] 具有迂腐潜力的len == 0,您如何看待strtok(line, "\n") 将典型的'\n' 归零?
  • @chux:好问题...但是:我没有assert(buflen &gt; 1 &amp;&amp; buflen &lt; sizeof(line)),但它们是合理的假设。在这种情况下,您唯一可以获得len == 0 的情况是没有数据可供读取,但报告为 EOF,因此我认为代码不会出现下溢。
  • 感谢您的考虑。注意:fgets() 读取 line(char 数组通常以 '\n' 结尾)而不是 C 字符串(char 数组以 '\0' 结尾)。所以fgets() 可以读取以'\0' 开头的罕见行,因此返回line 而不是EOF,而是strlen(line) == 0。这是一个奇怪的案例,我想你可能会感兴趣。
  • Grrr;谢谢@chux,我忘记了一个有效的用例!
【解决方案3】:

换行

char userName[8]  to  char userName[9]

char password[8]  to  char password[9]

因为那是您在fgets(userName, 9, stdin); 等中指定的内容。等等。

【讨论】:

    猜你喜欢
    • 2013-10-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-10-09
    • 2015-02-20
    • 1970-01-01
    相关资源
    最近更新 更多