1 00:00:00,000 --> 00:00:02,490 [Powered by Google Translate] [CS50 Бібліотека] 2 00:00:02,490 --> 00:00:04,220 [Nate Хардісон] [Harvard University] 3 00:00:04,220 --> 00:00:07,260 [Це CS50. CS50.TV] 4 00:00:07,260 --> 00:00:11,510 Бібліотека CS50 є корисним інструментом, який ми встановили на пристрій 5 00:00:11,510 --> 00:00:15,870 щоб зробити його простіше для вас, щоб написати програму, яка пропонує користувачеві для введення. 6 00:00:15,870 --> 00:00:21,670 У цьому відео ми будемо тягнути завісу і подивимося, що саме знаходиться в CS50 бібліотеки. 7 00:00:21,670 --> 00:00:25,520 >> У відео на бібліотеки C, ми говоримо про те, як ви # включити заголовки файлів 8 00:00:25,520 --> 00:00:27,570 бібліотеки у вихідний код, 9 00:00:27,570 --> 00:00:31,150 а потім ви пов'язуєте з двійкового файлу бібліотеки в стадії компонування 10 00:00:31,150 --> 00:00:33,140 з процесу компіляції. 11 00:00:33,140 --> 00:00:36,440 В заголовку файлу вказується інтерфейс бібліотеки. 12 00:00:36,440 --> 00:00:41,280 Тобто, вони докладно всі ресурси, що в бібліотеці доступні для використання, 13 00:00:41,280 --> 00:00:45,250 як функція декларацій, константи і типи даних. 14 00:00:45,250 --> 00:00:48,890 Двійковий файл бібліотеки містить реалізацію бібліотеки, 15 00:00:48,890 --> 00:00:54,580 який складається із заголовка бібліотеки, файли і бібліотеки. с файлами вихідного коду. 16 00:00:54,580 --> 00:00:59,820 >> Двійкового файлу бібліотеки не дуже цікаво подивитися на, так як це, ну, в двійковій системі. 17 00:00:59,820 --> 00:01:03,300 Отже, давайте поглянемо на файли заголовків для бібліотеки замість цього. 18 00:01:03,300 --> 00:01:07,710 У цьому випадку є тільки один заголовок файлу з ім'ям cs50.h. 19 00:01:07,710 --> 00:01:11,040 Ми встановили його в користувальницький каталог включає 20 00:01:11,040 --> 00:01:15,150 разом з файлами інших бібліотек системи заголовку. 21 00:01:15,150 --> 00:01:21,530 >> Одна з перших речей, які ви помітите, що cs50.h # включає в себе заголовні файли з інших бібліотек - 22 00:01:21,530 --> 00:01:25,670 поплавця, обмеження, стандартні логічний, і стандартні LIB. 23 00:01:25,670 --> 00:01:28,800 Знову ж, дотримуючись принципу не винаходити колесо, 24 00:01:28,800 --> 00:01:33,490 ми побудували CS0 бібліотеки за допомогою інструментів, які інші надана для нас. 25 00:01:33,490 --> 00:01:38,690 >> Наступна річ, яку ви побачите в бібліотеці є те, що ми визначаємо новий тип, званий «струни». 26 00:01:38,690 --> 00:01:42,330 Ця лінія дійсно тільки створює псевдонім для символьного типу *, 27 00:01:42,330 --> 00:01:46,000 так що це не може магічним чином надати новий тип рядки з атрибутами 28 00:01:46,000 --> 00:01:49,650 зазвичай пов'язані з строкових об'єктів на інших мовах, 29 00:01:49,650 --> 00:01:50,850 таких як довжина. 30 00:01:50,850 --> 00:01:55,180 Тому ми зробили це, аби убезпечити нових програмістів криваві подробиці 31 00:01:55,180 --> 00:01:57,580 покажчиків, поки вони не готові. 32 00:01:57,580 --> 00:02:00,130 >> Наступна частина заголовка файлу декларації функцій 33 00:02:00,130 --> 00:02:04,410 CS50, що бібліотека надає разом з документацією. 34 00:02:04,410 --> 00:02:06,940 Зверніть увагу на рівень деталізації в коментарях тут. 35 00:02:06,940 --> 00:02:10,560 Це супер важливо, щоб люди знали, як використовувати ці функції. 36 00:02:10,560 --> 00:02:19,150 Ми заявляємо, в свою чергу, функціями, щоб спонукати користувачів та повернення символів, двомісні, поплавки, цілими, 37 00:02:19,150 --> 00:02:24,160 давно мріє, і рядки, використовуючи наш власний тип рядка. 38 00:02:24,160 --> 00:02:26,260 Дотримуючись принципу приховування інформації, 39 00:02:26,260 --> 00:02:31,640 ми поставили нашим визначенням в окремому файлі реалізації з -. cs50.c-- 40 00:02:31,640 --> 00:02:35,110 розташований в каталозі джерело користувач. 41 00:02:35,110 --> 00:02:38,040 Ми за умови, що файл, так що ви можете поглянути на неї, 42 00:02:38,040 --> 00:02:41,490 дізнатися від нього, і перекомпілювати його на різних машинах, якщо ви хочете, 43 00:02:41,490 --> 00:02:45,510 навіть якщо ми думаємо, що краще працювати на приладі для цього класу. 44 00:02:45,510 --> 00:02:47,580 У будь-якому випадку, давайте подивимося на це зараз. 45 00:02:49,020 --> 00:02:54,620 >> Функції GetChar, GetDouble, GetFloat, GetInt, і GetLongLong 46 00:02:54,620 --> 00:02:58,160 Все побудовано на вершині GetString функції. 47 00:02:58,160 --> 00:03:01,510 Виявляється, що всі вони слідують по суті тією ж схемою. 48 00:03:01,510 --> 00:03:04,870 Вони використовують той час як цикл, щоб спонукати користувачів для однієї рядком введення. 49 00:03:04,870 --> 00:03:08,430 Вони повертають спеціальне значення, якщо користувач вводить порожню рядок. 50 00:03:08,430 --> 00:03:11,750 Вони намагаються розібрати входу користувача в якості відповідного типу, 51 00:03:11,750 --> 00:03:15,010 будь це символ, подвійні, з плаваючою точкою, і т.д. 52 00:03:15,010 --> 00:03:18,710 І тоді вони або повертають результат, якщо вхід був успішно завершений 53 00:03:18,710 --> 00:03:21,330 або вони reprompt користувач. 54 00:03:21,330 --> 00:03:24,230 >> На високому рівні, немає нічого дійсно складно тут. 55 00:03:24,230 --> 00:03:28,760 Ви могли б написати так само структурований код собі в минулому. 56 00:03:28,760 --> 00:03:34,720 Мабуть, самий загадковий вигляд частини Sscanf виклик, який аналізує введені користувачем дані. 57 00:03:34,720 --> 00:03:38,160 Sscanf є частиною перетворення сім'ї формат вводу. 58 00:03:38,160 --> 00:03:42,300 Він живе в io.h стандарт, і його робота полягає в розібрати рядок C, 59 00:03:42,300 --> 00:03:46,520 у відповідності з певним форматом, зберігання розбору результатів в змінну 60 00:03:46,520 --> 00:03:48,720 надаються абоненту. 61 00:03:48,720 --> 00:03:53,570 Оскільки вхідний функції перетворення формату дуже корисно, широко використовувані функції 62 00:03:53,570 --> 00:03:56,160 , Які не є супер інтуїтивно спочатку, 63 00:03:56,160 --> 00:03:58,300 ми підемо над тим, як Sscanf працює. 64 00:03:58,300 --> 00:04:03,330 >> Перший аргумент Sscanf це символ * - покажчик на символ. 65 00:04:03,330 --> 00:04:05,150 Для того щоб функція працювала правильно, 66 00:04:05,150 --> 00:04:08,340 , Що символ повинен бути першим символом рядка C, 67 00:04:08,340 --> 00:04:12,270 припиняється з нульовим \ 0 характеру. 68 00:04:12,270 --> 00:04:15,120 Це рядок для розбору 69 00:04:15,120 --> 00:04:18,269 Другий аргумент Sscanf це формат рядка, 70 00:04:18,269 --> 00:04:20,839 зазвичай передається у вигляді рядка постійної, 71 00:04:20,839 --> 00:04:24,040 і ви могли бачити рядок, як це раніше при використанні Printf. 72 00:04:24,040 --> 00:04:28,650 Знак відсотка в рядку формату вказує на специфікатор перетворення. 73 00:04:28,650 --> 00:04:30,850 Характер відразу після знаку відсотка, 74 00:04:30,850 --> 00:04:35,430 вказує на тип C, який ми хочемо Sscanf перетворення. 75 00:04:35,430 --> 00:04:40,090 У GetInt, ви бачите, що є д% і% о. 76 00:04:40,090 --> 00:04:48,690 Це означає, що Sscanf постараємося десятковій INT -% D - і символ -% с. 77 00:04:48,690 --> 00:04:51,510 Для кожного специфікатор перетворення у формат рядка, 78 00:04:51,510 --> 00:04:56,620 Sscanf очікує, що відповідний аргумент пізніше в своєму списку аргументів. 79 00:04:56,620 --> 00:05:00,850 Цей аргумент повинен вказувати на місце розташування належним типізованих 80 00:05:00,850 --> 00:05:04,000 , В якому зберігається результат перетворення. 81 00:05:04,000 --> 00:05:08,910 >> Типовий спосіб зробити це полягає у створенні змінної в стеку перед викликом Sscanf 82 00:05:08,910 --> 00:05:11,440 Для кожного елемента, який ви хочете, щоб розібрати з рядка 83 00:05:11,440 --> 00:05:15,520 , А потім використовувати оператор адреси - амперсанд - передати покажчики 84 00:05:15,520 --> 00:05:19,100 в цих змінних на заклик Sscanf. 85 00:05:19,100 --> 00:05:22,720 Ви можете бачити, що в GetInt ми робимо саме це. 86 00:05:22,720 --> 00:05:28,240 Прямо перед виклику Sscanf, ми заявляємо Int називається п і символ з виклику в стеку, 87 00:05:28,240 --> 00:05:32,340 і ми переходимо покажчики на них у виклику Sscanf. 88 00:05:32,340 --> 00:05:35,800 Підставляючи ці змінні в стеку є кращим у порівнянні з використанням простору, виділеного 89 00:05:35,800 --> 00:05:39,350 в купі з Танос, так як дозволяє уникнути накладних витрат на Танос виклику, 90 00:05:39,350 --> 00:05:43,060 і вам не доведеться турбуватися про витік пам'яті. 91 00:05:43,060 --> 00:05:47,280 Персонажі не передує знаком відсотка не підкажіть перетворення. 92 00:05:47,280 --> 00:05:50,380 Швидше вони просто додати в специфікації формату. 93 00:05:50,380 --> 00:05:56,500 >> Наприклад, якщо в рядку формату в GetInt були% D замість цього, 94 00:05:56,500 --> 00:05:59,800 Sscanf буде шукати літери слідують INT, 95 00:05:59,800 --> 00:06:04,360 і поки він буде намагатися перетворити Int, він не буде робити нічого іншого с. 96 00:06:04,360 --> 00:06:07,440 Єдине виключення з цього пробілу. 97 00:06:07,440 --> 00:06:11,030 Прогалини в рядку формату відповідає будь-яку кількість прогалин - 98 00:06:11,030 --> 00:06:12,890 навіть немає взагалі. 99 00:06:12,890 --> 00:06:18,100 Отже, ось чому коментаря згадує, можливо, з провідними і / або кінцевих пробілів. 100 00:06:18,100 --> 00:06:22,910 Таким чином, на даний момент це виглядає як виклик нашим Sscanf постараємося розібрати рядка введення користувачем 101 00:06:22,910 --> 00:06:25,380 , Перевіряючи можливі провідні пробіли, 102 00:06:25,380 --> 00:06:29,300 слід Int, який буде конвертований та збережений в Int змінної п 103 00:06:29,300 --> 00:06:33,090 слід деяку кількість прогалин, і слід символ 104 00:06:33,090 --> 00:06:35,810 зберігатися в символ змінної с. 105 00:06:35,810 --> 00:06:37,790 >> Що про повернення значення? 106 00:06:37,790 --> 00:06:41,560 Sscanf буде аналізувати вхідні лінії від початку до кінця, 107 00:06:41,560 --> 00:06:44,860 зупинці, коли вона досягає кінця або коли персонаж у вхідний 108 00:06:44,860 --> 00:06:49,320 не відповідає формату характер або коли він не може зробити перетворення. 109 00:06:49,320 --> 00:06:52,690 Прийшов повернення використовується виділити, коли він зупинився. 110 00:06:52,690 --> 00:06:55,670 Якщо він зупинився, тому що він досяг кінця вхідного рядка 111 00:06:55,670 --> 00:07:00,630 Перед виконанням будь-яких перетворень і перед відмовою у відповідності з частиною рядка формату, 112 00:07:00,630 --> 00:07:04,840 Потім спеціальна константа EOF повертається. 113 00:07:04,840 --> 00:07:08,200 В іншому випадку вона повертає кількість успішних переходів, 114 00:07:08,200 --> 00:07:14,380 який може бути 0, 1, або 2, так як ми попросив два перетворення. 115 00:07:14,380 --> 00:07:19,000 У нашому випадку, ми хочемо переконатися, що користувач ввів в Int і тільки Int. 116 00:07:19,000 --> 00:07:23,370 >> Отже, ми хочемо, щоб повернутися Sscanf 1. Дізнатися, чому? 117 00:07:23,370 --> 00:07:26,850 Якщо Sscanf повернула 0, то перетворення не були зроблені, 118 00:07:26,850 --> 00:07:31,690 так що користувач ввів щось інше, ніж Int на початку вході. 119 00:07:31,690 --> 00:07:37,100 Якщо Sscanf повертає 2, то користувач не правильно ввести його на початку введення, 120 00:07:37,100 --> 00:07:41,390 але потім вони ввели в деяких не-символу після 121 00:07:41,390 --> 00:07:44,940 з% з успішність перетворення. 122 00:07:44,940 --> 00:07:49,570 Нічого собі, це досить розлоге пояснення для одного виклику функції. 123 00:07:49,570 --> 00:07:53,460 У будь-якому випадку, якщо ви хочете більше інформації про Sscanf і його брати і сестри, 124 00:07:53,460 --> 00:07:57,130 перевірити чоловіка сторінки, Google, або обох. 125 00:07:57,130 --> 00:07:58,780 Є багато варіантів формату рядка, 126 00:07:58,780 --> 00:08:03,830 і вони можуть зберегти вам багато ручної праці при спробі розібрати рядки в C. 127 00:08:03,830 --> 00:08:07,180 >> Остання функція в бібліотеці дивитися на це GetString. 128 00:08:07,180 --> 00:08:10,310 Виявляється, що GetString є складною функцією, щоб написати правильно, 129 00:08:10,310 --> 00:08:14,290 навіть якщо вона здається такою простою, загальна задача. 130 00:08:14,290 --> 00:08:16,170 Чому це так? 131 00:08:16,170 --> 00:08:21,380 Ну, давайте подумаємо про те, як ми збираємося зберігати рядок, користувач вводить дюйма 132 00:08:21,380 --> 00:08:23,880 Так як рядок являє собою послідовність символів, 133 00:08:23,880 --> 00:08:26,430 ми могли б зберігати його в масив в стеку, 134 00:08:26,430 --> 00:08:31,250 але ми повинні знати, як довго масиву буде, коли ми оголосимо його. 135 00:08:31,250 --> 00:08:34,030 Точно так само, якщо ми хочемо, щоб покласти його на купу, 136 00:08:34,030 --> 00:08:38,090 ми повинні перейти до Танос кількість байт, ми хочемо резерву, 137 00:08:38,090 --> 00:08:39,730 Але це неможливо. 138 00:08:39,730 --> 00:08:42,760 Ми поняття не маємо, скільки символів користувач буде вводити 139 00:08:42,760 --> 00:08:46,590 перш ніж користувач насправді їх набору. 140 00:08:46,590 --> 00:08:50,720 >> Наївне вирішення цієї проблеми, це просто залишаємо за собою великий шматок простору, скажімо, 141 00:08:50,720 --> 00:08:54,540 Блок з 1000 символів для введення користувача, 142 00:08:54,540 --> 00:08:57,980 за умови, що користувач ніколи б не ввести в рядок, який довго. 143 00:08:57,980 --> 00:09:00,810 Це погана ідея з двох причин. 144 00:09:00,810 --> 00:09:05,280 По-перше, передбачається, що користувачі зазвичай не вводьте в рядку так довго, 145 00:09:05,280 --> 00:09:07,610 Ви могли б витрачати багато пам'яті. 146 00:09:07,610 --> 00:09:10,530 На сучасних машинах, це не може бути проблемою, якщо ви робите це 147 00:09:10,530 --> 00:09:13,890 в одному або двох окремих випадках, 148 00:09:13,890 --> 00:09:17,630 Але якщо ви приймаєте вході користувача в петлю і зберіганню для подальшого використання, 149 00:09:17,630 --> 00:09:20,870 Ви можете швидко поглинати тонни пам'яті. 150 00:09:20,870 --> 00:09:24,450 Крім того, якщо програма, яку ви пишете для меншого комп'ютера - 151 00:09:24,450 --> 00:09:28,100 пристрої, наприклад, смартфона або щось інше з обмеженим обсягом пам'яті - 152 00:09:28,100 --> 00:09:32,060 це рішення може викликати проблеми набагато швидше. 153 00:09:32,060 --> 00:09:36,450 Друга, більш серйозна причина, щоб не робити цього в тому, що він покидає вашу програму вразливою 154 00:09:36,450 --> 00:09:39,710 те, що називається атака переповнення буфера. 155 00:09:39,710 --> 00:09:45,840 У програмуванні, буфер пам'яті, що використовується для тимчасового зберігання вхідних або вихідних даних, 156 00:09:45,840 --> 00:09:48,980 який в даному випадку є наш 1000-символів блоків. 157 00:09:48,980 --> 00:09:53,370 Переповнення буфера відбувається, коли дані записуються в кінці минулого блок. 158 00:09:53,370 --> 00:09:57,790 >> Наприклад, якщо користувач насправді типу в більш ніж 1000 символів. 159 00:09:57,790 --> 00:10:01,570 Ви, можливо, випробували це випадково при програмуванні з масивами. 160 00:10:01,570 --> 00:10:05,620 Якщо у вас є масив з 10 цілих чисел, ніщо не заважає вам намагаюсь читати, ні писати 161 00:10:05,620 --> 00:10:07,810 15 Int. 162 00:10:07,810 --> 00:10:10,000 Є ніяких попереджень компілятора або помилки. 163 00:10:10,000 --> 00:10:13,250 Програма просто промахи вперед і звертається до пам'яті 164 00:10:13,250 --> 00:10:18,150 де вона думає, що 15-Int буде, і це може перезаписати інші змінні. 165 00:10:18,150 --> 00:10:22,040 У гіршому випадку, ви можете перезаписати деякі внутрішні вашої програми 166 00:10:22,040 --> 00:10:26,820 механізми контролю, в результаті чого ваша програма насправді виконувати різні інструкції 167 00:10:26,820 --> 00:10:28,340 ніж ви розраховували. 168 00:10:28,340 --> 00:10:31,360 >> Так от, це не є загальною для цього випадково, 169 00:10:31,360 --> 00:10:35,150 Але це досить поширена техніка, погані хлопці використовувати, щоб розбити програм 170 00:10:35,150 --> 00:10:39,080 і покласти шкідливого коду на комп'ютери інших людей. 171 00:10:39,080 --> 00:10:42,910 Таким чином, ми не можемо просто використовувати наші наївні рішення. 172 00:10:42,910 --> 00:10:45,590 Нам потрібно знайти спосіб, щоб наші програми з уразливими 173 00:10:45,590 --> 00:10:47,880 до атаки переповнювання буфера. 174 00:10:47,880 --> 00:10:51,430 Щоб зробити це, ми повинні переконатися, що наші буфера може рости, як ми читаємо 175 00:10:51,430 --> 00:10:53,850 більше вхідних даних від користувача. 176 00:10:53,850 --> 00:10:57,440 Рішення? Ми використовуємо буфера купи виділяється. 177 00:10:57,440 --> 00:10:59,950 Так як ми можемо змінити його розмір допомоги зміни розміру перерозподілити функції, 178 00:10:59,950 --> 00:11:04,580 і ми відстежуємо два числа - індекс наступної порожній слот в буфері 179 00:11:04,580 --> 00:11:08,390 і довжину або ємність буфера. 180 00:11:08,390 --> 00:11:13,210 Ми читаємо в символи від користувачів по одному використанням fgetc функції. 181 00:11:13,210 --> 00:11:19,360 Аргумент fgetc функція приймає - стандартний ввід - це посилання на стандартну рядок введення, 182 00:11:19,360 --> 00:11:23,810 яка є з'єднувачем вхідного каналу, який використовується для передачі користувальницького введення 183 00:11:23,810 --> 00:11:26,270 від терміналу до програми. 184 00:11:26,270 --> 00:11:29,890 >> Всякий раз, коли користувач вводить нового персонажа, ми перевіряємо, якщо індекс 185 00:11:29,890 --> 00:11:35,810 на наступний вільний слот плюс 1 більше, ніж ємність буфера. 186 00:11:35,810 --> 00:11:39,690 +1 Приходить, тому що якщо наступний вільний індекс дорівнює 5, 187 00:11:39,690 --> 00:11:44,150 Потім довжину наш буфер повинен бути 6 Спасибі 0, індексація. 188 00:11:44,150 --> 00:11:48,350 Якщо ми не вистачити місця в буфері, то ми намагаємося змінити її розмір, 189 00:11:48,350 --> 00:11:51,690 подвоєння його так, що ми скоротили кількість разів, що ми змінюємо 190 00:11:51,690 --> 00:11:54,760 якщо користувач друкує в дуже довгий рядок. 191 00:11:54,760 --> 00:11:57,950 Якщо рядок став занадто довгим або якщо ми запустимо з купи пам'яті, 192 00:11:57,950 --> 00:12:01,350 Ми звільнимо наші буфера і повертає нуль. 193 00:12:01,350 --> 00:12:04,170 >> Нарешті, ми додаємо символ в буфер. 194 00:12:04,170 --> 00:12:08,200 Як тільки користувач натискає введіть або повернути, сигналізація з нового рядка, 195 00:12:08,200 --> 00:12:12,050 або спеціальний символ - управління D - який сигналізує про кінець введення, 196 00:12:12,050 --> 00:12:16,240 Ми робимо перевірку, щоб побачити, якщо користувач насправді набрали ні в що. 197 00:12:16,240 --> 00:12:18,820 Якщо ні, то ми повертаємо нуль. 198 00:12:18,820 --> 00:12:22,280 В іншому випадку, тому що наш буфер, ймовірно, більше, ніж потрібно, 199 00:12:22,280 --> 00:12:24,830 У гіршому випадку це майже вдвічі більше, ніж нам потрібно 200 00:12:24,830 --> 00:12:27,830 так як ми двічі кожного разу, коли ми змінюємо розмір, 201 00:12:27,830 --> 00:12:31,840 ми робимо нову копію рядка, використовуючи тільки обсяг, який нам потрібен. 202 00:12:31,840 --> 00:12:34,220 Ми додали додатковий 1 до Танос виклику, 203 00:12:34,220 --> 00:12:37,810 так що є простір для спеціального символу нульового термінатора - \ 0, 204 00:12:37,810 --> 00:12:41,990 які ми додаємо в рядок раз ми копіюємо в інших персонажів, 205 00:12:41,990 --> 00:12:45,060 використання з'гпсру замість зЬгсру 206 00:12:45,060 --> 00:12:48,830 так що ми можемо точно вказати, скільки символів ми хочемо скопіювати. 207 00:12:48,830 --> 00:12:51,690 StrCpy копіює, поки не зустріне \ 0. 208 00:12:51,690 --> 00:12:55,740 Тоді ми звільнимо наші буфер і повернути копію з абонентом. 209 00:12:55,740 --> 00:12:59,840 >> Хто знав, що таке просте на вигляд функція може бути так складно? 210 00:12:59,840 --> 00:13:02,820 Тепер ви знаєте, що відбувається в CS50 бібліотеки. 211 00:13:02,820 --> 00:13:06,470 >> Мене звати Нейт Хардісон, і це CS50. 212 00:13:06,470 --> 00:13:08,350 [CS50.TV]