#c #scanf
#c #сканф #scanf
Вопрос:
У меня есть текстовый файл, подобный этому:
milk l 300
oil l 200
..
и моя функция заключается в следующем (некоторые переменные являются глобальными, некоторые слова написаны на итальянском языке).:
void Calcola() {
FILE *fp;
fp = fopen("ingredients_out.txt", "r");
float somma = 0.0;
int i;
char ingredient[15]; //this is the problem
char simbolo[1];
float quantita;
while(fscanf(fp, "%s%s%f", ingredient, simbolo, amp;quantita) == 3){
printf("%sn", ingredient); //I think the problem is here
if(simbolo[0] != 'g') {
for(i = 0; i < 4; i ) {
if(strcmp(V[i].ingrediente, ingredient) == 0) {
somma = quantita * V[i].peso;
}
}
}
}
fclose(fp);
printf("Somma pesi: %dn", somma);
}
Проблема в том, что переменная «ингредиент» всегда пуста, это серия пробелов .. Почему?
Комментарии:
1.
char simbolo[1];
как C-строка, она может содержать только нулевой терминатор, так что она в принципе бесполезна. Может быть, изменить это наchar simbolo;
и использовать%c
вfscanf
.2. или лучше написать
char simbolo[2]
, чтобы иметь пустую позицию для завершающего значения null…3. @SergeBallesta Вам нужно будет использовать
%1s
, чтобы он не записывал дальше массива
Ответ №1:
На самом деле это довольно интересно. Таким образом, причина на самом деле в том, что simbolo[1]
у него недостаточно памяти для хранения строки C (т.е. Строки, заканчивающиеся на a
). Но какое это имеет отношение к ingredient
пустому значению?
Ну, если вы посмотрите внимательно, самый первый символ ingredient
при первом чтении (то есть чтение «молоко») является нулевым терминатором, тогда i
, тогда l
, тогда k
. ПОДОЖДИТЕ! Как это
получилось, где m
должно было быть?
Ответ? Переполнение буфера.
fscanf
сначала помещает «молоко» ingredient
, как и должно быть. Затем он попытался вставить "l"
в simbolo
(примечание "l"
на самом деле l
и
), и он делает это по праву. Но подождите, simbolo
может содержать только один символ. Итак, он содержит l
, что, черт возьми, случилось с
?
Вы уже догадались. Из-за выравнивания стека на современных архитектурах ingredient
память начинается сразу после окончания simbolo
.
На самом деле So вводится, ingredient[0]
и boom вы только что уничтожили эту память.
Это довольно хрестоматийное определение неопределенного поведения, связанного с переполнением буфера. Не ожидайте, что это произойдет на каждой отдельной машине. Но я почти уверен, что это то, что происходит на вашем компьютере.
Так что да, исправление заключается в том, чтобы просто сделать char simbolo[2]
, если вы хотите, чтобы это l
было как строка. Или, если вы уверены, что это всегда единственный символ, просто выполните char simbolo
и используйте %c
на fscanf
Редактировать: также, как упоминалось @Barmar, %s
with simbolo[2]
alone не спасет вас, если вы не уверены в длине входного файла и не доверяете ему на 100%. Если вы настаиваете на использовании %s
, также укажите a n
в %ns
где n
— количество символов (исключая нулевой ограничитель), которое может содержать ваш буфер.
Комментарии:
1. Если вы не уверены, что это всегда один символ,
[2]
он также не будет достаточно большим. Это должно быть 1 максимально возможное значение.2. И при использовании
%c
перед ними должен быть пробел. В противном случае он поместит пробел вsimbolo
.3. О да! Большое спасибо, друг! Вы были действительно точны!
4. @Barmar очень хорошие замечания!
%s
в одиночку определенно небезопасно5. Вы можете использовать
%1s
для предотвращения переполнения, но если слово длиннее 1 символа, оно не будет читать следующееfloat
.