Specifying the maximum line length for scanf dynamically in C (for example, "% * s" in printf)

I can specify the maximum number of characters for scanf to read in buffer using this method:

 char buffer[64]; /* Read one line of text to buffer. */ scanf("%63[^\n]", buffer); 

But what if we do not know the length of the buffer when we write the code? What if it is a function parameter?

 void function(FILE *file, size_t n, char buffer[n]) { /* ... */ fscanf(file, "%[^\n]", buffer); /* WHAT NOW? */ } 

This code is vulnerable to buffer overflows because fscanf does not know how large the buffer is.

I remember this before and started thinking that this is the solution to the problem:

 fscanf(file, "%*[^\n]", n, buffer); 

My first thought was that * in "%*[*^\n]" means that the maximum line size is passed by argument (in this case n ). This is the * value in printf .

When I checked the documentation for scanf , I found out that this means that scanf should reject the result [^\n] .

This somewhat disappointed me, as I believe that it would be a very useful ability to dynamically pass the buffer size for scanf .

Is there a way to pass the buffer size to scanf dynamically?

+8
c io size scanf buffer
source share
2 answers

Main answer

scanf() no analogue to the printf() * format specifier.

In Programming Practices , Kernigan and Pike recommend using snprintf() to create a format string:

 size_t sz = 64; char format[32]; snprintf(format, sizeof(format), "%%%zus", sz); if (scanf(format, buffer) != 1) { …oops… } 

Additional Information

Updating the example to the full function:

 int read_name(FILE *fp, char *buffer, size_t bufsiz) { char format[16]; snprintf(format, sizeof(format), "%%%zus", bufsiz - 1); return fscanf(fp, format, buffer); } 

This emphasizes that the size in the format specification is smaller than the size of the buffer (this is the number of non-zero characters that can be saved, not counting the terminating zero). Note that this is different from fgets() , where the size (a int , by the way, is not a size_t ) - this is the size of the buffer, and not one less. There are several ways to improve a function, but it shows a point. (You can replace s in the format [^\n] if that is what you want.)

Also, as Tim Hour noted in the comments , if you want (the rest of) the input line, you are usually better off using fgets() to read the line, but remember that it includes a new line in its output (while %63[^\n] leaves a newline for reading by the next I / O operation). For a more general scan (for example, 2 or 3 lines) this method might be better; especially if they are used with fgets() or getline() and then sscanf() to parse the input.

In addition, the TR 24731-1 secure access functions implemented by Microsoft (more or less) and standardized in Appendix K of ISO / IEC 9899-2011 (C11 standard) require a certain length:

 if (scanf_s("%[^\n]", buffer, sizeof(buffer)) != 1) ...oops... 

This avoids buffer overflows, but probably generates an error if the input is too long. The size can / should be specified in the format string, as before:

 if (scanf_s("%63[^\n]", buffer, sizeof(buffer)) != 1) ...oops... if (scanf_s(format, buffer, sizeof(buffer)) != 1) ...oops... 

Note that a warning (from some compilers under some flag sets) about a "variable format string" should be ignored or suppressed for the code using the generated format string.

+9
source share

There really is no variable width specifier in the scanf family of functions. Alternatives include creating a format string dynamically (although this seems a bit silly if width is a compile-time constant) or just accepts a magic number. One possibility is to use preprocessor macros to indicate the width and width of the buffer line:

 #define STR_VALUE(x) STR(x) #define STR(x) #x #define MAX_LEN 63 char buffer[MAX_LEN + 1]; fscanf(file, "%" STR_VALUE(MAX_LEN) "[^\n]", buffer); 
+5
source share

All Articles