C-scanf()vs get()vs fgets())

C-scanf()vs get()vs fgets()),c,scanf,fgets,gets,C,Scanf,Fgets,Gets,我一直在做一个相当简单的程序,将字符串(假设输入了数字)转换为整数 完成后,我注意到一些我无法回答的非常奇怪的“bug”,主要是因为我对scanf()、get()和fgets()函数的工作原理了解有限。(不过我确实读了很多文学作品。) 因此,在不编写太多文本的情况下,以下是程序代码: #include <stdio.h> #define MAX 100 int CharToInt(const char *); int main() { char str[MAX];

我一直在做一个相当简单的程序,将字符串(假设输入了数字)转换为整数

完成后,我注意到一些我无法回答的非常奇怪的“bug”,主要是因为我对
scanf()
get()
fgets()
函数的工作原理了解有限。(不过我确实读了很多文学作品。)

因此,在不编写太多文本的情况下,以下是程序代码:

#include <stdio.h>

#define MAX 100

int CharToInt(const char *);

int main()
{
    char str[MAX];

    printf(" Enter some numbers (no spaces): ");
    gets(str);
//  fgets(str, sizeof(str), stdin);
//  scanf("%s", str);

    printf(" Entered number is: %d\n", CharToInt(str));

    return 0;
}

int CharToInt(const char *s)
{
    int i, result, temp;

    result = 0;
    i = 0;

    while(*(s+i) != '\0')
    {
        temp = *(s+i) & 15;
        result = (temp + result) * 10;
        i++;
    }

    return result / 10;
}
#包括
#定义最大值100
int CharToInt(常量字符*);
int main()
{
字符str[MAX];
printf(“输入一些数字(无空格):”;
获取(str);
//fgets(str,sizeof(str),stdin);
//scanf(“%s”,str);
printf(“输入的编号为:%d\n”,ChartPoint(str));
返回0;
}
int ChartPoint(常量字符*s)
{
int i,结果,温度;
结果=0;
i=0;
而(*(s+i)!='\0')
{
温度=*(s+i)和15;
结果=(温度+结果)*10;
i++;
}
返回结果/10;
}
这就是我一直遇到的问题。首先,当使用
gets()
函数时,程序工作正常

其次,当使用
fgets()
时,结果稍有错误,因为显然
fgets()
函数最后读取换行符(ASCII值10),这会导致结果出错

第三,当使用
scanf()
函数时,结果是完全错误的,因为第一个字符显然具有-52 ASCII值。对此,我没有任何解释

现在我知道不鼓励使用
gets()
,所以我想知道是否可以在这里使用
fgets()
,这样它就不会读取(或忽略)换行符。
另外,这个程序中的
scanf()
函数是怎么处理的?

您不应该使用
get
,这是正确的。如果要使用
fgets
,只需覆盖换行符即可

char *result = fgets(str, sizeof(str), stdin);
char len = strlen(str);
if(result != NULL && str[len - 1] == '\n')
{
  str[len - 1] = '\0';
}
else
{
  // handle error
}
这并不假设没有嵌入的空值。另一个选项是POSIX:

getline
的优点是它为您进行分配和重新分配,处理可能的嵌入空值,并返回计数,这样您就不必在
strlen
上浪费时间。请注意,不能将数组与
getline
一起使用。指针必须为空或可自由移动


我不确定您在使用
scanf

时遇到了什么问题是的,您希望避免
get
<如果缓冲区足够大,可以容纳新行,则code>fgets将始终读取新行(这可以让您知道缓冲区太小时,还有更多的行等待读取)。如果您想要像
fgets
这样的东西不能读取新行(丢失缓冲区太小的指示),您可以将
fscanf
与扫描集转换一起使用,例如:
%N[^\N]“
,其中“N”被缓冲区大小-1替换

使用
fgets
读取后,从缓冲区中删除尾随新行的一种简单(如果奇怪)方法是:
strtok(buffer,“\n”)
这不是strtok的预期使用方式,但我经常以这种方式使用它,而不是以预期的方式(我通常避免使用这种方式)。

尝试将fgets()与此修改后的ChartPoint()一起使用:

它本质上验证输入数字,并忽略任何其他内容。这是非常粗糙的,所以请根据口味修改它和盐。

永远不要使用gets(),它可能会导致无法忍受的溢出。如果您的字符串数组大小为1000,并且我输入了1001个字符,则我可以使您的程序缓冲区溢出。

  • 切勿使用
    获取
    。它不提供针对缓冲区溢出漏洞的保护(即,您无法告诉它您传递给它的缓冲区有多大,因此它无法阻止用户输入比缓冲区大的行并破坏内存)

  • 避免使用
    scanf
    。如果不小心使用,它可能会出现与
    gets
    相同的缓冲区溢出问题。即使忽略这一点

  • 通常,您应该改用
    fgets
    ,尽管有时会不方便(你必须去掉换行符,你必须提前确定缓冲区的大小,然后你必须弄清楚如何处理过长的行-你是否保留你读的部分,放弃整个内容,动态增加缓冲区,然后再试一次,等等。)。有一些非标准函数可为您执行此动态分配(例如POSIX系统上的
    getline
    ,函数)。请注意,
    ggets
    具有类似
    gets
    的语义,因为它为您去除了一个尾随换行符


所以我不是一个很好的程序员,但让我试着回答你关于
scanf();
的问题。我认为scanf非常好,几乎可以用于所有事情,没有任何问题。但是你的结构并不完全正确。它应该是:

char str[MAX];
printf("Enter some text: ");
scanf("%s", &str);
fflush(stdin);
变量前面的“&”很重要。它告诉程序在哪里(哪个变量)保存扫描的值。
fflush(stdin);
从标准输入(键盘)中清除缓冲区,因此不太可能出现缓冲区溢出

gets/scanf和fgets之间的区别在于
gets();
scanf();
只扫描到第一个空格
'
,而
fgets();
扫描整个输入。(但之后一定要清理缓冲区,以免以后出现溢出)

此代码存在许多问题。我们将修复名称不正确的变量和函数,并调查这些问题:

  • 首先,
    ChartPoint()
    应重命名为适当的
    StringToInt()
    ,因为它操作的是字符串,而不是单个字符

  • 函数
    chartPoint()
    [sic.]不安全。它不会检查用户是否意外传入空指针

  • 它不会验证输入,或者更准确地说,跳过无效输入
    int CharToInt(const char *s)
    {
        int i, result, temp;
    
        result = 0;
        i = 0;
    
        while(*(s+i) != '\0')
        {
            if (isdigit(*(s+i)))
            {
                temp = *(s+i) & 15;
                result = (temp + result) * 10;
            }
            i++;
        }
    
        return result / 10;
    }
    
    char str[MAX];
    printf("Enter some text: ");
    scanf("%s", &str);
    fflush(stdin);
    
    n = x*10 = x*8 + x*2
    
    #include <stdio.h>
    #include <ctype.h> // isdigit()
    
    // 1 fgets
    // 2 gets
    // 3 scanf
    #define INPUT 1
    
    #define SIGNED 1
    
    // re-implementation of atoi()
    // Test Case: 2147483647 -- valid    32-bit
    // Test Case: 2147483648 -- overflow 32-bit
    int StringToInt( const char * s )
    {
        int result = 0, prev, msb = (sizeof(int)*8)-1, overflow;
    
        if( !s )
            return result;
    
        while( *s )
        {
            if( isdigit( *s ) ) // Alt.: if ((*s >= '0') && (*s <= '9'))
            {
                prev     = result;
                overflow = result >> (msb-2); // test if top 3 MSBs will overflow on x*8
                result  *= 10;
                result  += *s++ & 0xF;// OPTIMIZATION: *s - '0'
    
                if( (result < prev) || overflow ) // check if would overflow
                    return prev;
            }
            else
                break; // you decide SKIP or BREAK on invalid digits
        }
    
        return result;
    }
    
    // Test case: 4294967295 -- valid    32-bit
    // Test case: 4294967296 -- overflow 32-bit
    unsigned int StringToUnsignedInt( const char * s )
    {
        unsigned int result = 0, prev;
    
        if( !s )
            return result;
    
        while( *s )
        {
            if( isdigit( *s ) ) // Alt.: if (*s >= '0' && *s <= '9')
            {
                prev    = result;
                result *= 10;
                result += *s++ & 0xF; // OPTIMIZATION: += (*s - '0')
    
                if( result < prev ) // check if would overflow
                    return prev;
            }
            else
                break; // you decide SKIP or BREAK on invalid digits
        }
    
        return result;
    }
    
    int main()
    {
        int  detect_buffer_overrun = 0;
    
        #define   BUFFER_SIZE 2    // set to small size to easily test overflow
        char str[ BUFFER_SIZE+1 ]; // C idiom is to reserve space for the NULL terminator
    
        printf(" Enter some numbers (no spaces): ");
    
    #if   INPUT == 1
        fgets(str, sizeof(str), stdin);
    #elif INPUT == 2
        gets(str); // can overflows
    #elif INPUT == 3
        scanf("%s", str); // can also overflow
    #endif
    
    #if SIGNED
        printf(" Entered number is: %d\n", StringToInt(str));
    #else
        printf(" Entered number is: %u\n", StringToUnsignedInt(str) );
    #endif
        if( detect_buffer_overrun )
            printf( "Input buffer overflow!\n" );
    
        return 0;
    }