【问题标题】:Method to safely get a string in C在 C 中安全获取字符串的方法
【发布时间】:2016-02-06 21:32:14
【问题描述】:

我对 C 非常陌生,想编写一个函数来安全地提示用户输入字符串并返回它。虽然我认为我已经处理了安全部分,但我无法将字符串返回到 main 方法并将其打印出来。这是我的代码

//Method to safely read user input
char * getFileName(char* fileName) {
    char* str[50];
    int isValid = 0;
    while (isValid == 0) {
        isValid = 1;
        printf("Enter file name: ");
        fgets(str, 50, stdin);
        if (strlen(str) == 49 && str[48] != '\n') { //http://stackoverflow.com/questions/21691843/how-to-correctly-input-a-string-in-c
            isValid = 0;
            if (strlen(str) > 0 && str[strlen(str) - 1] != '\n') {//http://stackoverflow.com/questions/35136026/simple-loops-and-string-length-in-c?noredirect=1#comment58000759_35136026
                printf("Error! String is too long\n\n");
                do {
                    fgets(str, 50, stdin);
                } while (strlen(str) > 0 && str[strlen(str) - 1] != '\n');
            }
        }
        for (int i = 0; i < strlen(str); i++) {
            if (str[i] == '%') {
                printf("Error: Attempted string format attack\n\n");
                isValid = 0;
            }
        }
    }
    for (int i = 0; i < strlen(str); i++) {
        fileName[i] = str[i];
    }
    return fileName;
}

int main()
{
    //Opening text file
    char fileName[50];
    getFileName(fileName);
    printf(fileName);

    printf("Press Enter to continue...");
    getchar();
    //
    return 0;
}

运行此代码会打印此结果

我不知道为什么。任何帮助将不胜感激!

【问题讨论】:

  • strlen() 迭代它的参数以计算它的长度、存储它的值并使用它而不是一遍又一遍地调用它。
  • 你想做什么?这段代码看起来不必要地复杂。
  • C 不支持方法,只支持函数。并且从不 printf 直接使用外部提供的字符串(即没有像"%s" 这样的格式字符串)。
  • @kaylum 看看if (str[i] == '%') 好像是故意printf(fileName)
  • @iharob:大体上同意,但是一个好的编译器会知道纯函数并正确标记库(或使用像 glibc 这样的库)。

标签: c arrays function


【解决方案1】:

因为您从不复制 null 终止符。这个

for (int i = 0; i < strlen(str); i++)
    fileName[i] = str[i];

只复制所有字符,但不复制 null 终止符。你可以使用strcpy(),因为你在过度使用strlen()时包含了string.h

这样试试

int i;
for (i = 0; str[i] != '\0' ; ++i)
    fileName[i] = str[i];
fileName[i] = str[i];

它应该可以按您的预期工作,并且确实避免像这样使用strlen()

你还有一个大问题,

char *str[50];

是一个由 50 个 char 指针组成的数组,这似乎不是您想要的,而是

char str[50];

你的代码太复杂,很难理解,我想这就是你想要的

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

char *
getFileName(char *str, size_t size)
{
    int isValid = 0;
    while (isValid == 0)
    {
        size_t length;
        isValid = 1;

        printf("Enter file name: ");
        if (fgets(str, size, stdin) == NULL)
            return NULL;
        length = strlen(str);
        if ((length == size - 1) && (str[size - 2] != '\n'))
        {
            isValid = 0;
            if ((length > 0) && (str[length - 1] != '\n'))
            {
                printf("Error! String is too long\n\n");
                continue;
            }
        }

        if (strchr(str, '%') != NULL)
        {
            printf("Error: Attempted string format attack\n\n");
            isValid = 0;
        }
    }
    return str;
}

int main()
{
    char array[100];
    if (getFileName(array, sizeof(array)) == NULL)
        fprintf(stderr, "error: input problem?\n");
    else
        printf(array);
    return 0;
}

【讨论】:

  • 解决了,谢谢!我会在 7 分钟内接受答案
【解决方案2】:

虽然您发现了导致初始问题的 nul-terminator 问题,但您在接受输入时还需要解决一些其他问题。您选择fgets 是一个不错的选择。然而,像所有面向行的 输入函数一样,它会读取到并包括 换行 ('\n') 到您的缓冲区中。这会让你的缓冲区在最后嵌入一个'\n'。作为输入例程的一部分,您需要删除或修剪尾随换行符。否则,这将在以后引起问题(想象一下调用strcat 以稍后添加到缓冲区中)。

处理删除是直截了当的。为了帮助自己完成此任务,当您在函数中调用 strlen 时,请保存结果。这将避免在您的函数中重复调用strlen。在已知长度的情况下,您可以执行测试以检查是否还有其他字符未读,并且在该测试之后,您知道buffer[len-1] 是否存在'\n'。如果存在,只需用 nul-terminating 字符覆盖'\n' 即可删除尾随的换行符。例如

buf[--length] = 0;  /* remove newline, decrement length */

同时减少长度,将长度更新为当前长度,以反映删除换行符。由于您费力地计算了作为输入的字符串的长度,因此最好将长度提供给调用函数,而不必重复 strlen 调用。

由于您要返回一个指向保存输入的缓冲区的指针,因此一种方法是简单地将 pointer 的长度作为 参数 传递给您的输入函数,并用字符串的长度更新指针值。这样长度只计算一次,并且在函数返回时立即可供调用函数使用。

此外,您可以使用strchr 函数来检查您在输入中选择的无效字符。如果您想将多个值视为无效值,或者它们根据您的输入而改变,您可以将包含每个字符的字符串作为参数传递给您的函数。如果发现无效字符,则提出另一种考虑。虽然您的错误检查很好,但如果您确实发现了无效字符,您可以通过将字符串中的第一个字符设置为 nul-terminating来将缓冲区设置为 empty-string > 字符。

您也可以通过将 prompt 作为参数传递给函数来增加输入函数的灵活性。这样一来,您就拥有了一个可以提示任何输入的函数,而不是提示并返回文件名的函数。一般的想法是摆脱函数中的硬编码值,而是将变化的值作为参数传递给函数。你也可以对你的字符数限制做同样的事情。因此,您可以拥有一个通用输入函数,可以将任何长度的字符串读入足够大小的字符数组,而不是将函数限制为提示/读取 49 个字符的文件名。

将这些部分放在一起,您可以大大提高输入功能的灵活性。以下是一个简短的示例函数,它将更改信息作为参数传递。看看它,如果你有任何问题,请告诉我。希望该示例能够为您提供一些在用 C 语言编写任何函数时要考虑的一般注意事项。祝您的编码好运:

char *getstr_input (char *buf, size_t max, size_t *n, const char *prompt)
{
    if (!buf) return NULL;
    if (*prompt) printf ("%s : ", prompt);      /* prompt      */
    if (!fgets (buf, max, stdin)) return NULL;  /* read input  */

    if (!(*n = strlen (buf))) return NULL;      /* test strlen */
    if (*n + 1 == max && buf[*n-1] != '\n') {   /* test short-read */
        fprintf (stderr, "warning: short-read, characters remain unread.\n");
    }
    else  /* remove trailing '\n' */
        buf[--*n] = 0;

    if (strchr (buf, '%')) { /* validate unwanted characters  */
        fprintf (stderr, "error: invalid character in string.\n");
        *buf = *n = 0;       /* set to empty-string, 0 length */
        return NULL;
    }
    return buf;
}

其中'buf' 的大小足以容纳'max' 字符,返回时包含'n' 字符(不包括nul-terminating 字符)并提示用户使用@ 987654337@ 输入。一个简短的测试程序可以是:

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

enum { MAX = 50 };  /* create a constant 50, adjust as desired */

int main (void) {

    char fname[MAX] = {0};
    size_t len = 0;

    getstr_input (fname, MAX, &len, "enter a filename");

    printf ("\n filename : %s\n length   : %zu\n\n", fname, len);

    return 0;
}

使用/输出示例

$ ./bin/fgets_prompt
enter a filename : 12345678

 filename : 12345678
 length   : 8

$ ./bin/fgets_prompt
enter a filename : 12345%6
error: invalid character in string.

 filename :
 length   : 0

【讨论】:

    猜你喜欢
    • 2016-07-26
    • 1970-01-01
    • 1970-01-01
    • 2019-10-01
    • 1970-01-01
    • 1970-01-01
    • 2015-09-22
    • 2011-07-11
    • 2020-11-20
    相关资源
    最近更新 更多