Вы должны только приводить строковые аргументы к макросу, и они должны быть вне строк или символьных констант в тексте замены макроса. Таким образом, вы, вероятно, должны использовать:
#define READIN(a, b) do { if (scanf("%" #a, &b) != 1) \
{ fprintf(stderr, "ERROR\n"); return EXIT_FAILURE; } \
} while (0)
int main(void)
{
unsigned int stack_size;
printf("Type in size: ");
READIN(u, stack_size);
printf("You entered %u\n", stack_size);
return(0);
}
Есть много изменений. Идиома do { ... } while (0)
предотвращает получение ошибок компиляции при таких обстоятельствах, как:
if (i > 10)
READIN(u, j);
else
READIN(u, k);
С вашим макросом вы получите сообщение типа unexpected keyword 'else'
, потому что точка с запятой после первого READIN()
будет пустой инструкцией после встроенного if
, поэтому else
не может принадлежать видимый if
или if
внутри макроса.
Тип stack_size
равен unsigned int
; следовательно, правильный спецификатор формата - u
(d
для подписи int
).
И, что самое важное, аргумент a
в макросе правильно структурирован (и конкатенация строк смежных строковых литералов - чрезвычайно полезная особенность C89! - позаботится обо всем остальном. И аргумент b
в макрос тоже не встраивается в строку.
Отчет об ошибках передается в stderr
(стандартный поток сообщений об ошибках), и сообщение заканчивается новой строкой, поэтому оно действительно появится. Я не заменил return EXIT_FAILURE;
на exit(EXIT_FAILURE);
, но это, вероятно, будет разумным выбором, если макрос будет использоваться за пределами main()
. Это предполагает, что «прекращение при ошибке» - это, в первую очередь, правильное поведение. Часто это не для интерактивных программ, но исправить это немного сложнее.
Я также игнорирую свои оговорки по поводу использования scanf()
вообще; Я обычно избегаю этого, потому что нахожу восстановление после ошибок слишком сложным. Я программировал на C всего около 28 лет, и мне все еще трудно scanf()
контролировать, поэтому я по сути никогда не использую его. Я обычно использую fgets()
и sscanf()
вместо этого. Среди других достоинств я могу сообщить о строке, которая вызвала проблему; это трудно сделать, когда scanf()
, возможно, сожрал некоторые из них.
Моя мысль с scanf()
заключается в том, чтобы читать только в положительных числах и без букв. Мой общий код создает стек, который пользователь вводит, и тип должен быть только положительным, в противном случае ошибка. [...] Я только хотел узнать, есть ли лучшее решение, запрещающее пользователю вводить что-то кроме положительных чисел?
Я только что попробовал приведенный выше код (с добавлением #include <stdlib.h>
и #include <stdio.h>
), ввел -2
и получил 4294967294, что не то, что я хотел (формат %u
не отклоняет -2
, по крайней мере на MacOS X 10.7.2). Итак, я бы пошел с fgets()
и strtoul()
, скорее всего. Тем не менее, точное обнаружение всех возможных проблем с strtoul()
является проявлением некоторой деликатности.
Это альтернативный код, который я придумал:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <limits.h>
#include <string.h>
int main(void)
{
unsigned int stack_size = 0;
char buffer[4096];
printf("Type in size: ");
if (fgets(buffer, sizeof(buffer), stdin) == 0)
printf("EOF or error detected\n");
else
{
char *eos;
unsigned long u;
size_t len = strlen(buffer);
if (len > 0)
buffer[len - 1] = '\0'; // Zap newline (assuming there is one)
errno = 0;
u = strtoul(buffer, &eos, 10);
if (eos == buffer ||
(u == 0 && errno != 0) ||
(u == ULONG_MAX && errno != 0) ||
(u > UINT_MAX))
{
printf("Oops: one of many problems occurred converting <<%s>> to unsigned integer\n", buffer);
}
else
stack_size = u;
printf("You entered %u\n", stack_size);
}
return(0);
}
Спецификация strtoul()
приведена в ISO / IEC 9899: 1999 § 7.20.1.4:
¶1 [...]
unsigned long int strtoul(const char * restrict nptr,<br>
char ** restrict endptr, int base);
[...]
№2 [...] Во-первых,
они разбивают входную строку на три части: начальную, возможно пустую, последовательность
символы пробела (как указано в функции isspace
), последовательность субъекта
напоминающие целое число, представленное в некотором радиксе, определяемом значением основания, и
конечная строка из одного или нескольких нераспознанных символов, включая завершающий ноль
символ входной строки. Затем они пытаются преобразовать последовательность объекта в
целое число и возвращает результат.
¶3 [...]
¶4 Предметная последовательность определяется как самая длинная начальная подпоследовательность входной строки,
начиная с первого символа, не являющегося пробелом, который имеет ожидаемую форму. Предмет
последовательность не содержит символов, если входная строка пуста или полностью состоит из белого
пробел, или если первый символ, не являющийся пробелом, отличается от знака или допустимой буквы
или цифра.
¶5 Если последовательность объекта имеет ожидаемую форму, а значение базы равно нулю, последовательность
символов, начинающихся с первой цифры, интерпретируется как целочисленная константа в соответствии с
правила 6.4.4.1. Если предметная последовательность имеет ожидаемую форму и значение базы
находится между 2 и 36, он используется в качестве основы для преобразования, приписывая каждой букве свое значение
как указано выше. Если предметная последовательность начинается со знака минус, значение, полученное из
преобразование отменяется (в типе возврата). Указатель на последнюю строку хранится в
объект, на который указывает endptr, при условии, что endptr не является нулевым указателем.
¶6 [...]
¶7 Если последовательность объекта пуста или не имеет ожидаемой формы, преобразование не выполняется.
выполнила; значение nptr
сохраняется в объекте, указанном endptr
, при условии
что endptr
не является нулевым указателем.
Возвращает
¶8 Функции strtol
, strtoll
, strtoul
и strtoull
возвращают преобразованные
значение, если есть. Если преобразование не может быть выполнено, возвращается ноль. Если правильное значение
находится вне диапазона представимых значений, LONG_MIN, LONG_MAX, LLONG_MIN,
LLONG_MAX, ULONG_MAX или ULLONG_MAX возвращается (в соответствии с типом возврата
и знак значения, если оно есть), а значение макроса ERANGE хранится в errno.
Ошибка, которую я получил от 64-битной компиляции, где -2
был преобразован в 64-битную длину без знака, и это было вне диапазона, приемлемого для 32-битной unsigned int
(условие сбоя было u > UINT_MAX
). Когда я перекомпилировал в 32-битном режиме (т. Е. sizeof(unsigned int) == sizeof(unsigned long)
), значение -2
снова было принято, снова интерпретировано как 4294967294. Таким образом, даже это не является достаточно деликатным ... вам, вероятно, придется вручную пропустить начальные пробелы и отклонить отрицательный знак (и, возможно, тоже положительный знак; вам также потребуется #include <ctype.h>
):
char *bos = buffer;
while (isspace(*bos))
bos++;
if (!isdigit(*bos))
...error - not a digit...
char *eos;
unsigned long u;
size_t len = strlen(bos);
if (len > 0)
bos[len - 1] = '\0'; // Zap newline (assuming there is one)
errno = 0;
u = strtoul(bos, &eos, 10);
if (eos == bos ||
(u == 0 && errno != 0) ||
(u == ULONG_MAX && errno != 0) ||
(u > UINT_MAX))
{
printf("Oops: one of many problems occurred converting <<%s>> to unsigned integer\n", buffer);
}
Как я уже сказал, весь процесс довольно нетривиален.
( Глядя на это снова, я не уверен, что предложение u == 0 && errno != 0
когда-либо будет ловить какие-либо ошибки ... возможно, не потому, что условие eos == buffer
(или eos == bos
) ловит случай, когда ничего нет конвертировать вообще. )