2014 dxdy logo

Научный форум dxdy

Математика, Физика, Computer Science, Machine Learning, LaTeX, Механика и Техника, Химия,
Биология и Медицина, Экономика и Финансовая Математика, Гуманитарные науки




Начать новую тему Ответить на тему
 
 scanf в массив
Сообщение21.10.2021, 02:42 


06/04/18

323
https://docs.microsoft.com/ru-ru/cpp/c- ... w=msvc-160

В следующем примере спецификация ширины равна 20; это означает, что из входного потока могут быть прочитаны до 20 символов. Длина буфера равна 21, что включает место для возможных 20 символов плюс завершающий нуль-символ:
Используется синтаксис C++
char str[21];
scanf_s("%20s", str, 21);
Что-то я не понял. Когда определяется массив в инструкции char str[21];, то в ячейку str[21] автоматически попадает завершающий нуль-символ. Кроме этого у нас есть 21 свободная ячейка с номерами в диапазоне от нуля до 20. Зачем надо дополнительно записывать ещё один завершающий нуль-символ вызовом scanf ?

-- 20.10.2021, 23:43 --

https://www.cplusplus.com/reference/cst ... /?kw=scanf

Тут то же самое в разделе Example.
Используется синтаксис C++
  char str [80];
 
  printf ("Enter your family name: ");
  scanf ("%79s",str);
 
Зачем так делать ?

 Профиль  
                  
 
 Re: scanf в массив
Сообщение21.10.2021, 03:55 
Заслуженный участник


04/05/09
4587
Qlin в сообщении #1535675 писал(а):
Когда определяется массив в инструкции char str[21];, то в ячейку str[21]
Нет. У такого массива нет элемента с индексом 21. Единственное, что по стандарту можно сделать с этим индексом - взять его адрес (указатель). Обращаться по этому адресу уже нельзя.

 Профиль  
                  
 
 Re: scanf в массив
Сообщение21.10.2021, 04:05 
Заслуженный участник


18/09/21
1756
Qlin в сообщении #1535675 писал(а):
Когда определяется массив в инструкции
Когда определяете структуру данных, то в неё ничего не пишется (если это не объект с конструктором). Для неё просто выделяется память, которая обычно содержит мусор.
Qlin в сообщении #1535675 писал(а):
Зачем надо дополнительно записывать ещё один завершающий нуль-символ вызовом scanf ?
scanf, как и любая другая функция записиывающая строку в область памяти, записывает её целиком, включая конечный нуль.
В C/C++ используются null-terminated strings. Конец строки обозначается символом ноль. (Например в Паскале строки по другому работали. Там в начале был счётчик количества символов, а нуля в конце не было.)
Вот строка "dxdy" займёт в C/С++ бять байт: 'd', 'x', 'd', 'y', '\0'.

 Профиль  
                  
 
 Re: scanf в массив
Сообщение21.10.2021, 05:08 
Заслуженный участник


20/08/14
11797
Россия, Москва
Qlin в сообщении #1535675 писал(а):
Зачем надо дополнительно записывать ещё один завершающий нуль-символ вызовом scanf ?
Qlin в сообщении #1535675 писал(а):
Зачем так делать ?
Затем что scanf/scanf_s не пишет "ещё один нуль", а берёт из входного потока указанное (или меньшее) количество символов и сохраняет их в указанную переменную (с добавлением завершающего нуля если это не запрещено строкой формата).

 Профиль  
                  
 
 Re: scanf в массив
Сообщение21.10.2021, 05:16 


30/01/17
245
Qlin в сообщении #1535675 писал(а):
Когда определяется массив в инструкции char str[21];, то в ячейку str[21] автоматически попадает завершающий нуль-символ.

Все что происходит - резервирование куска памяти из 21 элемента, к которому можно обращаться по имени str. Нумерация начинается с 0, поэтому под элемент str[21] память не зарезервирована. Больше никаких действий не выполняется, то есть элементы c 0-го по 20-й не инициализируются.

Qlin в сообщении #1535675 писал(а):
Зачем надо дополнительно записывать ещё один завершающий нуль-символ вызовом scanf ?

Далее scanf записывает данные в область памяти, которая начинается по адресу str, и в том числе 0 после последнего сохраненного в эту область памяти символа, чтобы код, который вызвал scanf, мог узнать длину строки, которую scanf записала в память, начиная с адреса str. То есть записывается один завершающий ноль символ, других там нет. Сам адрес str ничего не говорит scanf о размере куска памяти, который она может использовать. Поэтому scanf можно еще передать и информацию о том, сколько памяти, начиная с адреса str, можно использовать для записи строки ("%20s" - значит можно использовать 21 элемент).

Qlin в сообщении #1535675 писал(а):
Зачем так делать ?

Так нужно делать, чтобы scanf "знала" сколько памяти доступно и не попыталась использовать больше. Если информации о размере нет, то scanf считает, что можно использовать сколько угодно памяти, а у пользователя появляется возможность записать нечто в память за пределами зарезервированной области, там уже хранятся другие данные(в том числе служебные, код обработки которых автоматически генерируется компилятором). Найти такую ошибку будет сложно. Все может быть хуже. Например, программа считывает пароль, но не учитывает размер буфера. Тогда кто-то может попытаться подобрать специальную строку, которая при считывании изменит некоторые данные и заставит программу считать, что введен верный пароль.

 Профиль  
                  
 
 Re: scanf в массив
Сообщение21.10.2021, 08:00 


06/04/18

323
Используется синтаксис C++
#include <iostream>

int main() {
    char str[2];
    str[0] = 'a';
    str[1] = 'b';
    std::cout << str;
}
Почему тогда эта программа при выполнении не гуляет по памяти в поисках ближайшего завершающего символа, а всегда выводит ab ?

 Профиль  
                  
 
 Re: scanf в массив
Сообщение21.10.2021, 08:08 
Заслуженный участник


18/09/21
1756
Не всегда, а только потому что случайно оказался ноль после str.
Напечатайте int(str[2]) и посмотрите.

Вместо нуля там мог бы оказатся мусор, тогда мусор и напечатало бы.
Вот из таких проблем (когда не понимают, что делают) и возникают в C/C++ трудно отлаживаемые ошибки.

 Профиль  
                  
 
 Re: scanf в массив
Сообщение21.10.2021, 08:51 


30/01/17
245
Qlin в сообщении #1535691 писал(а):
Почему тогда эта программа при выполнении не гуляет по памяти в поисках ближайшего завершающего символа

Вы ошибаетесь. Эта программа ищет ближайший 0.

Qlin в сообщении #1535691 писал(а):
всегда выводит ab

Такие выводы нужно делать основываясь на стандарте (во время обучения можно полагаться на учебник)
Иначе Ваша программа будет работать не всегда и не у всех.

Попробуйте:
Используется синтаксис C++
#include <iostream>

int main() {
    char str[5];
    str[0] = 'a';
    str[1] = 'b';
    str[2] = 0;
    str[3] = 'a';
    str[4] = 'b';
    std::cout << str;
}

 Профиль  
                  
 
 Re: scanf в массив
Сообщение21.10.2021, 09:01 
Заслуженный участник


18/09/21
1756
Qlin в сообщении #1535691 писал(а):
Используется синтаксис C++
#include <iostream>

int main() {
char str[2];
str[0] = 'a';
str[1] = 'b';
std::cout << str;
}

Почему тогда эта программа

Этот код ошибочен. Даже если после компиляции он выдаёт правильный результат, он вполне может выдать неправильный результат, если что-то поменять (скомпилировать на другой системе, другим компилятором, изменить флаги компилятора или просто внести какие-то несвязанные изменения в программу).

Правильно так:
Используется синтаксис C++
#include <iostream>

int main() {
char str[3];
str[0] = 'a';
str[1] = 'b';
str[2] = '\0';
std::cout << str;
}

или так:
Используется синтаксис C++
#include <iostream>

int main() {
char str[2];
str[0] = 'a';
str[1] = '\0';
std::cout << str;
}


или вот так:
Используется синтаксис C++
#include <iostream>

int main() {
char str[2];
str[0] = 'a';
str[1] = 'b';
std::cout << str[0] << str[1];
}

 Профиль  
                  
 
 Re: scanf в массив
Сообщение21.10.2021, 10:48 


30/01/17
245
zykov в сообщении #1535700 писал(а):
Правильно так

А какие критерии используются для оценки "правильности" кода?

 Профиль  
                  
 
 Re: scanf в массив
Сообщение21.10.2021, 10:56 
Заслуженный участник
Аватара пользователя


16/07/14
9166
Цюрих
Qlin в сообщении #1535691 писал(а):
Почему тогда эта программа при выполнении не гуляет по памяти в поисках ближайшего завершающего символа, а всегда выводит ab ?
Потому что везет. AddressSanitizer такое ловит (да, у него бывают и false positive, но редко):
Код:
$ clang++ -fsanitize=address check.cpp && ./a.out
=================================================================
==3577641==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffce0c69e62 at pc 0x56510edf962b bp 0x7ffce0c69e10 sp 0x7ffce0c695c0
READ of size 7 at 0x7ffce0c69e62 thread T0
    #0 0x56510edf962a in __interceptor_strlen.part.0 (a.out+0x6862a)
    #1 0x7f04f88481be in std::char_traits<char>::length(char const*) /build/gcc/src/gcc-build/x86_64-pc-linux-gnu/libstdc++-v3/include/bits/char_traits.h:371:25

Ivan_B в сообщении #1535713 писал(а):
А какие критерии используются для оценки "правильности" кода?
Соответствие стандарту С++. Если из стандарта следует, что код делает то, что нужно - то код правильный, иначе - нет, даже если случайно работает.

 Профиль  
                  
 
 Re: scanf в массив
Сообщение21.10.2021, 12:08 


30/01/17
245
mihaild в сообщении #1535714 писал(а):
Соответствие стандарту С++. Если из стандарта следует, что код делает то, что нужно - то код правильный

Я к тому, что понимать правильность можно по-разному, что для начинающего не очевидно, что код, наблюдаемое поведение которого точно определено стандартом, и хороший код - не одно и то же.
Например, можно решить, что это правильный пример того как инициализировать буфер строкой.
Если очень нужно, то так (а не как в примере)
Используется синтаксис C++
char str[] = "asdf"


Еще один вопрос - для чего тут буфер, может достаточно так:
Используется синтаксис C++
const char* str = "asdf"


Также здесь некая смесь кода в стиле С с кодом на С++, которая компилируется С++ компилятором.
Если это С++, то какой стандарт используется, не используются ли возможности языка, которые при написании нового кода лучше не использовать.
Возможно что-то, что следовало использовать, не использовано.
Дальше можно задаться вопросом на сколько хорошо иметь зашитый в коде строковый литерал.
Поэтому я бы не говорил "так правильно" без каких-либо оговорок.

 Профиль  
                  
 
 Re: scanf в массив
Сообщение21.10.2021, 13:28 
Заслуженный участник
Аватара пользователя


16/07/14
9166
Цюрих
Ivan_B в сообщении #1535725 писал(а):
для начинающего не очевидно, что код, наблюдаемое поведение которого точно определено стандартом, и хороший код - не одно и то же
Про "хороший" код никто ничего не говорил. Говорили про ошибочность. И код, содержащий UB, должен считаться ошибочным, даже если в какой-то ситуации работает. Новичкам это не очевидно, поэтому им нужно про это сказать, что и было сделано.

 Профиль  
                  
 
 Re: scanf в массив
Сообщение21.10.2021, 19:01 
Заслуженный участник


04/05/09
4587
Ivan_B в сообщении #1535725 писал(а):
Если очень нужно, то так (а не как в примере)
Используется синтаксис C++
char str[] = "asdf"
Вот кстати, Qlin, напечатайте в этом варианте sizeof(str) и убедитесь, что компилятор включил завершающий нуль в размер массива.

 Профиль  
                  
 
 Re: scanf в массив
Сообщение28.10.2021, 20:19 
Аватара пользователя


28/10/21
100
Qlin в сообщении #1535675 писал(а):
Что-то я не понял. Когда определяется массив в инструкции char str[21];, то в ячейку str[21] автоматически попадает завершающий нуль-символ.


Это откуда вы такое взяли? Ничего подобного. Весь массив изначально содержит мусор (подразумевая автоматический массив). Кроме того, последняя ячейка такого массива - это `str[20]`. Не существует никакого `str[21]`.

Qlin в сообщении #1535675 писал(а):
Кроме этого у нас есть 21 свободная ячейка с номерами в диапазоне от нуля до 20. Зачем надо дополнительно записывать ещё один завершающий нуль-символ вызовом scanf ?


Но функция `scanf_s` не обязательно использует все 21 ячейки. Функция `scanf_s` запишет в этот массив строку, которая запросто может быть намного короче. Вот для того, чтобы пометить конец строки функция `scanf_s` и поместит в ее конце завершающий \0. Это само определение "строки" в языке С: строка по определению завершается нулевым символом.

Qlin в сообщении #1535675 писал(а):
Зачем так делать ?


Не совсем понимаю, о чем именно вопрос. Как делать?

-- 28.10.2021, 09:25 --

Ivan_B в сообщении #1535713 писал(а):
[quote="zykov в [url=http://dxdy.ru/post1535700.html#p1535700]
А какие критерии используются для оценки "правильности" кода?


Критерий должен быть ясен из контекста. Формальных устоявшихся определений нет.

Для языка С обычно "корректным" называют код, который соответствует синтаксису и ограничениям (cosntrаints), описанным в стандарте языка. (Это фактически требование выдачи диагностики, взятое из стандарта языка.) То есть "корректный" код - это формально компилируемый код. При этом "корректный" код может иметь неопределенное поведение. В языке С++ дела обстоят аналогично.

"Правильным" же обычно называют код, который не только "корректен", но еще и не страдает от неопределенного поведения. "Правильность" запросто может зависеть от платформы, но этот обычно ясно из контекста.

Например, программа Qlin из сообщения выше

Используется синтаксис C++
#include <iostream>

int main() {
    char str[2];
    str[0] = 'a';
    str[1] = 'b';
    std::cout << str;
}


является "корректной", но не "правильной". Ее поведение не определено.

 Профиль  
                  
Показать сообщения за:  Поле сортировки  
Начать новую тему Ответить на тему  [ Сообщений: 15 ] 

Модераторы: Karan, Toucan, PAV, maxal, Супермодераторы



Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей


Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете добавлять вложения

Найти:
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group