[Powered by Google Translate] [CS50 Бібліотека]
[Nate Хардісон] [Harvard University]
[Це CS50. CS50.TV]
Бібліотека CS50 є корисним інструментом, який ми встановили на пристрій
щоб зробити його простіше для вас, щоб написати програму, яка пропонує користувачеві для введення.
У цьому відео ми будемо тягнути завісу і подивимося, що саме знаходиться в CS50 бібліотеки.
>> У відео на бібліотеки C, ми говоримо про те, як ви # включити заголовки файлів
бібліотеки у вихідний код,
а потім ви пов'язуєте з двійкового файлу бібліотеки в стадії компонування
з процесу компіляції.
В заголовку файлу вказується інтерфейс бібліотеки.
Тобто, вони докладно всі ресурси, що в бібліотеці доступні для використання,
як функція декларацій, константи і типи даних.
Двійковий файл бібліотеки містить реалізацію бібліотеки,
який складається із заголовка бібліотеки, файли і бібліотеки. с файлами вихідного коду.
>> Двійкового файлу бібліотеки не дуже цікаво подивитися на, так як це, ну, в двійковій системі.
Отже, давайте поглянемо на файли заголовків для бібліотеки замість цього.
У цьому випадку є тільки один заголовок файлу з ім'ям cs50.h.
Ми встановили його в користувальницький каталог включає
разом з файлами інших бібліотек системи заголовку.
>> Одна з перших речей, які ви помітите, що cs50.h # включає в себе заголовні файли з інших бібліотек -
поплавця, обмеження, стандартні логічний, і стандартні LIB.
Знову ж, дотримуючись принципу не винаходити колесо,
ми побудували CS0 бібліотеки за допомогою інструментів, які інші надана для нас.
>> Наступна річ, яку ви побачите в бібліотеці є те, що ми визначаємо новий тип, званий «струни».
Ця лінія дійсно тільки створює псевдонім для символьного типу *,
так що це не може магічним чином надати новий тип рядки з атрибутами
зазвичай пов'язані з строкових об'єктів на інших мовах,
таких як довжина.
Тому ми зробили це, аби убезпечити нових програмістів криваві подробиці
покажчиків, поки вони не готові.
>> Наступна частина заголовка файлу декларації функцій
CS50, що бібліотека надає разом з документацією.
Зверніть увагу на рівень деталізації в коментарях тут.
Це супер важливо, щоб люди знали, як використовувати ці функції.
Ми заявляємо, в свою чергу, функціями, щоб спонукати користувачів та повернення символів, двомісні, поплавки, цілими,
давно мріє, і рядки, використовуючи наш власний тип рядка.
Дотримуючись принципу приховування інформації,
ми поставили нашим визначенням в окремому файлі реалізації з -. cs50.c--
розташований в каталозі джерело користувач.
Ми за умови, що файл, так що ви можете поглянути на неї,
дізнатися від нього, і перекомпілювати його на різних машинах, якщо ви хочете,
навіть якщо ми думаємо, що краще працювати на приладі для цього класу.
У будь-якому випадку, давайте подивимося на це зараз.
>> Функції GetChar, GetDouble, GetFloat, GetInt, і GetLongLong
Все побудовано на вершині GetString функції.
Виявляється, що всі вони слідують по суті тією ж схемою.
Вони використовують той час як цикл, щоб спонукати користувачів для однієї рядком введення.
Вони повертають спеціальне значення, якщо користувач вводить порожню рядок.
Вони намагаються розібрати входу користувача в якості відповідного типу,
будь це символ, подвійні, з плаваючою точкою, і т.д.
І тоді вони або повертають результат, якщо вхід був успішно завершений
або вони reprompt користувач.
>> На високому рівні, немає нічого дійсно складно тут.
Ви могли б написати так само структурований код собі в минулому.
Мабуть, самий загадковий вигляд частини Sscanf виклик, який аналізує введені користувачем дані.
Sscanf є частиною перетворення сім'ї формат вводу.
Він живе в io.h стандарт, і його робота полягає в розібрати рядок C,
у відповідності з певним форматом, зберігання розбору результатів в змінну
надаються абоненту.
Оскільки вхідний функції перетворення формату дуже корисно, широко використовувані функції
, Які не є супер інтуїтивно спочатку,
ми підемо над тим, як Sscanf працює.
>> Перший аргумент Sscanf це символ * - покажчик на символ.
Для того щоб функція працювала правильно,
, Що символ повинен бути першим символом рядка C,
припиняється з нульовим \ 0 характеру.
Це рядок для розбору
Другий аргумент Sscanf це формат рядка,
зазвичай передається у вигляді рядка постійної,
і ви могли бачити рядок, як це раніше при використанні Printf.
Знак відсотка в рядку формату вказує на специфікатор перетворення.
Характер відразу після знаку відсотка,
вказує на тип C, який ми хочемо Sscanf перетворення.
У GetInt, ви бачите, що є д% і% о.
Це означає, що Sscanf постараємося десятковій INT -% D - і символ -% с.
Для кожного специфікатор перетворення у формат рядка,
Sscanf очікує, що відповідний аргумент пізніше в своєму списку аргументів.
Цей аргумент повинен вказувати на місце розташування належним типізованих
, В якому зберігається результат перетворення.
>> Типовий спосіб зробити це полягає у створенні змінної в стеку перед викликом Sscanf
Для кожного елемента, який ви хочете, щоб розібрати з рядка
, А потім використовувати оператор адреси - амперсанд - передати покажчики
в цих змінних на заклик Sscanf.
Ви можете бачити, що в GetInt ми робимо саме це.
Прямо перед виклику Sscanf, ми заявляємо Int називається п і символ з виклику в стеку,
і ми переходимо покажчики на них у виклику Sscanf.
Підставляючи ці змінні в стеку є кращим у порівнянні з використанням простору, виділеного
в купі з Танос, так як дозволяє уникнути накладних витрат на Танос виклику,
і вам не доведеться турбуватися про витік пам'яті.
Персонажі не передує знаком відсотка не підкажіть перетворення.
Швидше вони просто додати в специфікації формату.
>> Наприклад, якщо в рядку формату в GetInt були% D замість цього,
Sscanf буде шукати літери слідують INT,
і поки він буде намагатися перетворити Int, він не буде робити нічого іншого с.
Єдине виключення з цього пробілу.
Прогалини в рядку формату відповідає будь-яку кількість прогалин -
навіть немає взагалі.
Отже, ось чому коментаря згадує, можливо, з провідними і / або кінцевих пробілів.
Таким чином, на даний момент це виглядає як виклик нашим Sscanf постараємося розібрати рядка введення користувачем
, Перевіряючи можливі провідні пробіли,
слід Int, який буде конвертований та збережений в Int змінної п
слід деяку кількість прогалин, і слід символ
зберігатися в символ змінної с.
>> Що про повернення значення?
Sscanf буде аналізувати вхідні лінії від початку до кінця,
зупинці, коли вона досягає кінця або коли персонаж у вхідний
не відповідає формату характер або коли він не може зробити перетворення.
Прийшов повернення використовується виділити, коли він зупинився.
Якщо він зупинився, тому що він досяг кінця вхідного рядка
Перед виконанням будь-яких перетворень і перед відмовою у відповідності з частиною рядка формату,
Потім спеціальна константа EOF повертається.
В іншому випадку вона повертає кількість успішних переходів,
який може бути 0, 1, або 2, так як ми попросив два перетворення.
У нашому випадку, ми хочемо переконатися, що користувач ввів в Int і тільки Int.
>> Отже, ми хочемо, щоб повернутися Sscanf 1. Дізнатися, чому?
Якщо Sscanf повернула 0, то перетворення не були зроблені,
так що користувач ввів щось інше, ніж Int на початку вході.
Якщо Sscanf повертає 2, то користувач не правильно ввести його на початку введення,
але потім вони ввели в деяких не-символу після
з% з успішність перетворення.
Нічого собі, це досить розлоге пояснення для одного виклику функції.
У будь-якому випадку, якщо ви хочете більше інформації про Sscanf і його брати і сестри,
перевірити чоловіка сторінки, Google, або обох.
Є багато варіантів формату рядка,
і вони можуть зберегти вам багато ручної праці при спробі розібрати рядки в C.
>> Остання функція в бібліотеці дивитися на це GetString.
Виявляється, що GetString є складною функцією, щоб написати правильно,
навіть якщо вона здається такою простою, загальна задача.
Чому це так?
Ну, давайте подумаємо про те, як ми збираємося зберігати рядок, користувач вводить дюйма
Так як рядок являє собою послідовність символів,
ми могли б зберігати його в масив в стеку,
але ми повинні знати, як довго масиву буде, коли ми оголосимо його.
Точно так само, якщо ми хочемо, щоб покласти його на купу,
ми повинні перейти до Танос кількість байт, ми хочемо резерву,
Але це неможливо.
Ми поняття не маємо, скільки символів користувач буде вводити
перш ніж користувач насправді їх набору.
>> Наївне вирішення цієї проблеми, це просто залишаємо за собою великий шматок простору, скажімо,
Блок з 1000 символів для введення користувача,
за умови, що користувач ніколи б не ввести в рядок, який довго.
Це погана ідея з двох причин.
По-перше, передбачається, що користувачі зазвичай не вводьте в рядку так довго,
Ви могли б витрачати багато пам'яті.
На сучасних машинах, це не може бути проблемою, якщо ви робите це
в одному або двох окремих випадках,
Але якщо ви приймаєте вході користувача в петлю і зберіганню для подальшого використання,
Ви можете швидко поглинати тонни пам'яті.
Крім того, якщо програма, яку ви пишете для меншого комп'ютера -
пристрої, наприклад, смартфона або щось інше з обмеженим обсягом пам'яті -
це рішення може викликати проблеми набагато швидше.
Друга, більш серйозна причина, щоб не робити цього в тому, що він покидає вашу програму вразливою
те, що називається атака переповнення буфера.
У програмуванні, буфер пам'яті, що використовується для тимчасового зберігання вхідних або вихідних даних,
який в даному випадку є наш 1000-символів блоків.
Переповнення буфера відбувається, коли дані записуються в кінці минулого блок.
>> Наприклад, якщо користувач насправді типу в більш ніж 1000 символів.
Ви, можливо, випробували це випадково при програмуванні з масивами.
Якщо у вас є масив з 10 цілих чисел, ніщо не заважає вам намагаюсь читати, ні писати
15 Int.
Є ніяких попереджень компілятора або помилки.
Програма просто промахи вперед і звертається до пам'яті
де вона думає, що 15-Int буде, і це може перезаписати інші змінні.
У гіршому випадку, ви можете перезаписати деякі внутрішні вашої програми
механізми контролю, в результаті чого ваша програма насправді виконувати різні інструкції
ніж ви розраховували.
>> Так от, це не є загальною для цього випадково,
Але це досить поширена техніка, погані хлопці використовувати, щоб розбити програм
і покласти шкідливого коду на комп'ютери інших людей.
Таким чином, ми не можемо просто використовувати наші наївні рішення.
Нам потрібно знайти спосіб, щоб наші програми з уразливими
до атаки переповнювання буфера.
Щоб зробити це, ми повинні переконатися, що наші буфера може рости, як ми читаємо
більше вхідних даних від користувача.
Рішення? Ми використовуємо буфера купи виділяється.
Так як ми можемо змінити його розмір допомоги зміни розміру перерозподілити функції,
і ми відстежуємо два числа - індекс наступної порожній слот в буфері
і довжину або ємність буфера.
Ми читаємо в символи від користувачів по одному використанням fgetc функції.
Аргумент fgetc функція приймає - стандартний ввід - це посилання на стандартну рядок введення,
яка є з'єднувачем вхідного каналу, який використовується для передачі користувальницького введення
від терміналу до програми.
>> Всякий раз, коли користувач вводить нового персонажа, ми перевіряємо, якщо індекс
на наступний вільний слот плюс 1 більше, ніж ємність буфера.
+1 Приходить, тому що якщо наступний вільний індекс дорівнює 5,
Потім довжину наш буфер повинен бути 6 Спасибі 0, індексація.
Якщо ми не вистачити місця в буфері, то ми намагаємося змінити її розмір,
подвоєння його так, що ми скоротили кількість разів, що ми змінюємо
якщо користувач друкує в дуже довгий рядок.
Якщо рядок став занадто довгим або якщо ми запустимо з купи пам'яті,
Ми звільнимо наші буфера і повертає нуль.
>> Нарешті, ми додаємо символ в буфер.
Як тільки користувач натискає введіть або повернути, сигналізація з нового рядка,
або спеціальний символ - управління D - який сигналізує про кінець введення,
Ми робимо перевірку, щоб побачити, якщо користувач насправді набрали ні в що.
Якщо ні, то ми повертаємо нуль.
В іншому випадку, тому що наш буфер, ймовірно, більше, ніж потрібно,
У гіршому випадку це майже вдвічі більше, ніж нам потрібно
так як ми двічі кожного разу, коли ми змінюємо розмір,
ми робимо нову копію рядка, використовуючи тільки обсяг, який нам потрібен.
Ми додали додатковий 1 до Танос виклику,
так що є простір для спеціального символу нульового термінатора - \ 0,
які ми додаємо в рядок раз ми копіюємо в інших персонажів,
використання з'гпсру замість зЬгсру
так що ми можемо точно вказати, скільки символів ми хочемо скопіювати.
StrCpy копіює, поки не зустріне \ 0.
Тоді ми звільнимо наші буфер і повернути копію з абонентом.
>> Хто знав, що таке просте на вигляд функція може бути так складно?
Тепер ви знаєте, що відбувається в CS50 бібліотеки.
>> Мене звати Нейт Хардісон, і це CS50.
[CS50.TV]