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]