简体   繁体   中英

Max string length using scanf -> ANSI C

I have:

#define MAX_STR_LEN 100

and I want to put into scanf pattern so I can control the string length:

scanf("%100[^\n]s",sometext)

I tried:

scanf("%MAX_STR_LEN[^\n]s",sometext)
scanf("%"MAX_STR_LEN"[^\n]s",sometext)
scanf("%",MAX_STR_LEN,"[^\n]s",sometext)

And it didn't work. I just want to avoid buffer overflow since "sometext" is allocated with malloc(MAX_STR_LEN) ...

Any ideas?

I wasn't happy with any of these solutions, so I researched further, and discovered GNU GCC macro stringification

which can be used as:

#define XSTR(A) STR(A)
#define STR(A) #A
#define MAX_STR_LEN 100
scanf("%"XSTR(MAX_STR_LEN)"[^\n]s", sometext)

Maybe VS2010 offers something similar?

I just want to avoid buffer overflow

Then don't use scanf() . At all.

If you are scanning lines of text, don't #define MAX_STR either. You can haz LINE_MAX in <limits.h> (if you are targeting POSIX compatible systems):

char buf[LINE_MAX];
fgets(buf, sizeof(buf), stdin);

should do the trick.

As almost everyone says, it is better to use fgets(..., stdin) to handle this problem.

In the following link, I have suggested a safe and correct technique that let you to replace scanf() by a safer method, by means of a solid macro :

A macro that safely replaces scanf()

The macro I have proposed (working with compatible C99 compilers) is safe_scanf() , as shown in the following program:

#include <stdio.h>
#define safe_scanf(fmt, maxb, ...) { \
    char buffer[maxb+1] = { [maxb - 1] = '\0' }; \
    fgets(buffer, maxb+1, stdin); \
    if ((buffer[maxb - 1] != '\0') && (buffer[maxb - 1] != '\n')) \
        while(getchar() != '\n') \
           ; \
    sscanf(buffer, fmt, __VA_ARGS__); \
  }

#define MAXBUFF 20     

int main(void) {
   int x; float f;      
   safe_scanf("%d %g", MAXBUFF+1, &x, &f);
   printf("Your input was: x == %d\t\t f == %g",  x, f);
   return 0;
}  

You would have to tune the value of MAXBUFF according to your needs...
Although the macro safe_scanf() is pretty solid,
there are some weakness in using the macro approach:
Missing type-checking for parameters, missing "return" values (which hardly differs from the "true" scanf() function, that returns an int , with valuable information for error checking), and so on.
All that problems have solution, but it is part of another topic...

Maybe, the most exact solution is to define a function my_scanf() with variable number of parameters, by invoking the stdarg.h library, joint to a combination of fgets() and vsscanf() . Here you have the code:

#include <stdio.h>
#include <stdarg.h>

int my_scanf(const char* fmt, const unsigned int maxbuff, ...) {
    va_list ptr;
    int ret;

    if (maxbuff <= 0)
       return EOF; /* Bad size for buffer[] */

    char buffer[maxbuff+1];
    buffer[maxbuff-1] = '\0';  /* Quick buffer cleaning... */

    if (fgets(buffer, maxbuff+1, stdin) == NULL)
       return EOF; /* Error detected */
    else {
        if ((buffer[maxbuff-1] != '\n') && (buffer[maxbuff-1] != '\0'))
            /* Condition logically equivalent to:
                   fgets() has not reached an '\n'
            */
            while (getchar() != '\n')
               ; /* "Flushing" stdin... */

        va_start(ptr, maxbuff);
        ret = vsscanf(buffer, fmt, ptr);
        va_end(ptr);
        return ret;
    }    
}

#define MAXBUFF 20
int main(void) {
   int x; 
   float z;
   int scanf_ret = my_scanf("%d %g", MAXBUFF, &x, &z);
   printf("\nTest:\n x == %d\n z == %g\n scanfret == %d", x, z, scanf_ret);
   getchar();   
   return 0;   
}

The function my_scanf() has the prototype

int my_scanf(const char* fmt, const int maxbuff, ...);

It accepts a format string fmt that behaves in the same way as any other scanf() -like does.
The 2nd parameter is the maximum number of chars that will be effectively accepted from the standard input (keyboard).
The return value is an int , which is EOF if maxbuff has no sense, or well some input error happened. If a non-negative value is returned, it is the same that would be returned by the standard functions sscanf() or vsscanf() .

Inside the function, maxbuff is incremented in 1, because fgets() makes some room for an additional '\\0' character.
Non-positive values of maxbuff are immediatelly discarded.
fgets() will read a string is read from stdin (keyboard) with room for at most maxbuff characters, including '\\n'.
If the user has entered a very long string, then it will bu truncated, and some kind of "flush" mechanism is necessary in order to discard all the characters to the next '\\n' ( ENTER ). If not, the next keyboard reading could have older characters, not desired at all.
The condition for "flushing" is that fgets() has not reached '\\n' after reading stdin .
This is the case if, and only if, buffer[maxbuff - 1] is not equal to '\\0' nor '\\n'.
( Check it! )
Finally, an appropiate combination of stdarg.h macros and the function vsscanf() is employed to process the variable list of parameters.

Recommend the fgets(buffer, sizeof(buffer), stdin) approach.

If you still want to use scanf() you can create its format at runtime.

#define MAX_STR_LEN 100
char format[2 + sizeof(size_t)*3 + 4 + 1];  // Ugly magic #
sprintf(format, " %%%zu[^\n]", (size_t) MAX_STR_LEN);
scanf(format, sometext);

or re-define MAX_STR_LEN to be a string

#define MAX_STR_LEN "100"
scanf(" %" MAX_STR_LEN "[^\n]", sometext);

Still recommend the fgets() .
Note fgets() will put leading spaces and the trailing \\n in your buffer whereas " %[^\\n]" will not.
BTW: the trailing s in your formats is not likely doing what you think.

怎么样

scanf("%.*[^\n]s", MAX_STR_LEN, sometext)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM