0

I've got an assignment where I have to acquire a string input whose length must not be greater than, say, 32. This length is given by a macro called MAX_BUF_LEN. I found solutions on StackOverflow where this restriction is given via a magic number. But is there any way to avoid this?

The solution I found was like:

char buf[MAX_BUF_LEN];
scanf("%"512"[^\n]", buf); 

Additionally, I found this solution both with the quotation marks before and behind the number 512; and without it. Is there any difference?

14
  • 1
    This line scanf("%"512"[^\n]", buf); will not compile. Commented Jun 6, 2024 at 16:55
  • 1
    The easiest way to get this right is to avoid scanf and use fgets instead: fgets(buf, sizeof buf, stdin); Commented Jun 6, 2024 at 16:59
  • 2
    @TedLyngmo Good point. trubefighter, please confirm that using scanf() is not actually required. I suspect it might be, for teaching purposes not about input reading but about macro useage. Is your programming class currently more concerned with input or with macros? Commented Jun 6, 2024 at 17:01
  • 1
    Yes, I meant "quotation marks". Please excuse this error; my English is not ideal. Commented Jun 6, 2024 at 17:42
  • 1
    You must always be able to handle buffer overflow. It's beginner's trap that you need to work with early. Another common mistake is to assign a buffer of what they think is exactly the right size. Be generous, and allow for accepting a reasonable amount of incorrect user input. Commented Jun 6, 2024 at 17:50

4 Answers 4

1

A define can be stringifed to use in a format string.
The value in the format string must be one less than the size of the array.

#include <stdio.h>

#define MAX_BUF_LEN 512

// stringify to use in format string
#define FS(x) SFS(x)
#define SFS(x) #x

int main ( void) {
    char buf[MAX_BUF_LEN + 1]; // +1 to allow for terminating zero
    scanf("%"FS(MAX_BUF_LEN)"[^\n]", buf);
}
Sign up to request clarification or add additional context in comments.

1 Comment

Dont know if I'm going to use this or if I, examply and contrary to my preceding plans, will use fgets. Either way, thank you for the good answer!
1

You can use the pre-processor with stringification, but the code is a lot cleaner if you just build the format string at run time. eg:

char buf[MAX_BUF_LEN];
char fmt[64];
snprintf(fmt, sizeof fmt, "%%%zu[^\\n]", sizeof buf - 1);
if (scanf(fmt, buf) == 1) { ...

2 Comments

Dont know if I'm going to use this or if I, examply and contrary to my preceding plans, will use fgets. Either way, thank you for the good answer!
It's almost certain that a space will be needed before the first % with the %[^\n] format specifier, because unlike %d and %f etc, leading whitespace is not filtered automatically.
0

Step 1: Fix array size

... acquire a string input whose length must not be greater than, say, 32.

Since the length of the string may be 32, the size of the array containing the string must be as least 33.
32 for the characters and 1 for the terminating null character.

// char buf[MAX_BUF_LEN];
char buf[MAX_BUF_LEN + 1];

Comments

0

Here is a brief summary:

  • You could use macros to stringify the maximum length of the string to input, not the buffer length, but this is very cumbersome and fragile as the macro MAX_BUF_LEN is not necessarily macro and might not expand to a single decimal number without a suffix (eg: #define MAX_BUF_LEN 32U or #define MAX_BUF_LEN (32))

  • you could construct the format string in a buffer with snprintf and call scanf() with this buffer as the format string, but this solution is also very cumbersome and calling scanf() with an opaque format string is risky as the compiler cannot check the types of the arguments for consistency.

  • the scanf() format string "%31[^\n]" is intrinsically problematic too:

    • the conversion will fail if there is a pending newline in the input stream (use " %31[^\n]" to avoid this)
    • there is no way to input an empty line
    • the trailing newline will stay in the input stream after scanf() returns, along with any remaining input if more than 31 bytes were input. Use " %31[^\n]%*[^\n]" to discard extra characters or " %31[^\n]%*1[\n]" to consume the newline, but you cannot have both at the same time with a single scanf() format.

I strongly suggest you avoid scanf() and use fgets() for this type on input, or better, a custom function modelled after get_string() for the cs50 library or something simpler like this:

#include <stdio.h>

// read a full string of input into a fixed length destination array
// return the number of characters read into the destination array
// truncation occurred if the return value is >= size.
// immediate end of file causes a return value of -1
int get_string(const char *prompt, char *buf, size_t size) {
    size_t i;
    int c;
    if (prompt) {
        printf("%s: ");
        fflush(stdout);
    }
    for (i = 0; (c = getchar()) != EOF && c != '\n'; i++) {
        if (i + 1 < size)
            buf[i] = (char)c;
    }
    if (i < size)
        buf[i] = '\0';
    else
    if (size > 0)
        buf[size - 1] = '\0';
    return (i == 0 && c == EOF) ? -1 : (int)i;
}

int main(void) {
    char buffer[MAX_BUF_LEN + 1];
    int len = get_string("Enter string", buffer, sizeof buffer);
    printf("length is %d\n", len);
    printf("string is >%s<\n", buffer);
    return 0;
}

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.