Как безопасно программировать вне среды управляемого кода?

Если вы программируете на C или C ++, не имея преимуществ управляемого языка, таких как управление памятью, проверка типов или защита от переполнения буфера, с использованием арифметики указателей, как вы можете убедиться, что ваши программы безопасны? Вы используете много юнит-тестов или просто осторожный программист? У вас есть другие методы?


person Community    schedule 07.10.2009    source источник
comment
Я действительно не жду множества ответов. Я подозреваю, что есть очень мало основных техник, которые использует большинство людей.   -  person Robert Harvey    schedule 07.10.2009
comment
C / C ++ имеет управление памятью (умные указатели). Это строго типизированный язык. Защита буфера не является обязательной (используйте at (), а не operator []). Так что это не значит, что мы здесь используем каменные ножи.   -  person Martin York    schedule 07.10.2009
comment
@Martin Обычно я не из тех, кто вдавался в подробности. Я буду жаловаться на кого-то, кто использовал C / C ++, но я думаю, что стоило бы просто сказать C ++ в этом утверждении. В C нет стандартных методов защиты памяти или проверки границ буфера.   -  person Falaina    schedule 07.10.2009
comment
@Falaina - если вы не имеете в виду инкапсуляцию гадостей или использование уже существующих библиотек, чтобы сделать это за вас, то же самое и C ++ - и оба они жизнеспособны в C.Например, я уверен, что помню хотя бы один C библиотека, которая выполняла примерно то же самое, что и потоки и строки стандартной библиотеки C ++ (соответствующая проблема заключалась в предотвращении переполнения буфера с помощью изменяемых размеров буферов), используя систему дескрипторов для скрытия внутренних компонентов.   -  person Steve314    schedule 07.10.2009
comment
Без этих современных преимуществ писать рабочие программы практически невозможно. Вот почему операционные системы все время падают.   -  person unwind    schedule 07.10.2009
comment
@Falaina - это не все или ничего. Рассмотрим, например, функцию strcpy языка C на языке C, для которой не проверяются границы. Теперь у нас также есть strncpy, у которого есть границы, и поэтому он рекомендуется практически во всех случаях, когда вы могли использовать strcpy раньше. То же самое верно для большинства функций обработки строк. Таким образом, во многих областях (но далеко не во всех) защита от переполнения буфера в новом коде C - это вопрос образования и выбора. В целом C ++, конечно, идет дальше, и, если вы следуете идиомам, он не намного опаснее, чем искаженный язык.   -  person philsquared    schedule 07.10.2009
comment
@Phil: Я бы никогда не рекомендовал strncpy (). Это странно и не так безопасно, как можно было ожидать.   -  person unwind    schedule 07.10.2009
comment
раскрутка: Мне действительно кажется, что n-функции (и подобные им) усложняют код. Вам нужен код, чтобы проверить, насколько велики буферы, независимо от того, используете ли вы n-вариант или нет. Используйте std::string человек. И если вы все еще используете C вместо C ++, не надо.   -  person Tom Hawtin - tackline    schedule 30.11.2009


Ответы (9)


Все вышеперечисленное. Я использую:

  1. Большая осторожность
  2. Умные указатели как можно больше
  3. Структуры данных, которые были протестированы, много стандартной библиотеки
  4. Юнит-тесты все время
  5. Инструменты проверки памяти, такие как MemValidator и AppVerifier
  6. Молитесь каждую ночь, чтобы он не вылетел на сайте клиента.

На самом деле я просто преувеличиваю. Это не так уж плохо и на самом деле не так уж и сложно контролировать ресурсы, если вы правильно структурируете свой код.

Интересное замечание. У меня есть большое приложение, которое использует DCOM и имеет управляемые и неуправляемые модули. Неуправляемые модули, как правило, труднее отлаживать во время разработки, но они очень хорошо работают на сайте заказчика из-за того, что на нем выполняется множество тестов. Управляемые модули иногда страдают от плохого кода, потому что сборщик мусора настолько гибкий, что программисты ленивы проверять использование ресурсов.

person Andrew Keith    schedule 07.10.2009
comment
Умные указатели - это то, о чем я раньше не слышал. Я должен это проверить. - person Robert Harvey; 07.10.2009
comment
Мне приходится использовать их много, так как меня мучает устаревший код COM. Без умных указателей я потеряю все эти ссылки и кровоизлияние в память. - person Andrew Keith; 07.10.2009
comment
У меня развилась аллергия на голые указатели в коде C ++. Если я увижу что-то одно, я склонен заключить его в умный указатель, даже если в этом нет необходимости. Инстинкт сослужил мне хорошую службу - я не припомню, чтобы у меня висела указка лет десять или больше. - person philsquared; 07.10.2009
comment
Интеллектуальные указатели IMO - это временная мера. Сборка мусора - это настоящее дело. Я думаю, что к настоящему времени хорошо известно, что интеллектуальные указатели (по крайней мере, разнообразие с подсчетом ссылок и разнообразие auto_ptr ‹T›, которое в любом случае имеет ограниченное использование) не могут соответствовать характеристикам производительности хорошо настроенного сборщика мусора. Но опять же, это от человека, который думает, что пришло время C ++ уйти на пенсию. - person Autodidact; 07.10.2009
comment
@ SDX2000: Я думаю, что большинство опытных разработчиков C ++ будут утверждать, что сборка мусора в лучшем случае неэффективна, а в худшем - костыль по сравнению с правильным использованием интеллектуальных указателей. Для C ++ доступны сборщики мусора, но они не пользуются популярностью из-за эффективной реализации и разнообразия доступных реализаций интеллектуальных указателей. Очевидно, ваше понимание интеллектуальных указателей, похоже, влияет на ваше мнение. Я предлагаю продолжить чтение о том, как и когда их использовать (поскольку auto_ptr не имеет ограниченного использования, он имеет очень точное и четко определенное использование (передача права собственности)). - person Martin York; 07.10.2009
comment
@ SDX2000: концепция отказа от языка смехотворна. Каждый язык хорош для решения задач в разных областях приложений. У C # / Java / C ++ / C есть разные (но частично совпадающие) области, где они сияют, и другие области, где они не так полезны. Вы не должны использовать язык, потому что это тот, который вы знаете, вы должны использовать язык, который лучше всего соответствует проблемной области, для которой вы пытаетесь написать программу. - person Martin York; 07.10.2009
comment
@Martin - Спасибо за добрые слова мудрости ... популярное мнение, кажется, остается разделенным, как обычно, например, см. stackoverflow.com/questions/867114/. Нет никаких оснований полагать, что один из механизмов сборки мусора работает быстрее / эффективнее, чем другой, без некоторой количественной оценки. И, к вашему сведению, мое понимание интеллектуальных указателей не так поверхностно, как вы думаете ... В некоторых случаях я писал TR1 / shared_ptr ‹T› как интеллектуальные указатели, и я хорошо знаю их сильные и слабые стороны. - person Autodidact; 08.10.2009
comment
@Martin - Отвечая на ваш второй комментарий, вы правы, это действительно смешно. Я должен был быть более конкретным, когда сказал, что C ++ должен уйти в отставку. Я имел в виду ... пришло время переоценить позицию C ++ как универсального инструмента для решения проблем и прекратить использование в тех областях, которые лучше обслуживаются другими современными языками. Если вы когда-либо работали с C #, вы знаете, что C ++ - это PITA. Я программировал на C ++ в течение последних 15 лет, мои навыки C ++ здесь не обсуждаются. - person Autodidact; 08.10.2009
comment
В интеллектуальных указателях нет ничего эффективного. Подсчет ссылок (при условии, что мы говорим об интеллектуальном указателе) смехотворно неэффективен по сравнению с приличным сборщиком мусора. Хороший программист на C ++ должен с этим согласиться. Сборщики мусора очень эффективны, гораздо эффективнее, чем примитивный пересчет, который мы используем в C ++. Умные указатели, конечно же, обладают и другими положительными качествами, которых GC не может предложить. Но производительности среди них нет. - person jalf; 01.12.2009

Я использую множество утверждений и создаю как «отладочную», так и «выпускную» версию. Моя отладочная версия работает намного медленнее, чем моя окончательная версия, со всеми проверками, которые она выполняет.

Я часто использую Valgrind, и мой код не имеет утечек памяти. Нуль. Гораздо проще сохранить программу без утечек, чем взять программу с ошибками и исправить все утечки.

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

Я пишу на чистом C (я не могу использовать C ++ в этом проекте), но я делаю C очень последовательным образом. У меня есть объектно-ориентированные классы с конструкторами и деструкторами; Приходится называть их вручную, но последовательность помогает. И если я забываю вызвать деструктор, Valgrind бьет меня по голове, пока я не исправлю его.

В дополнение к конструктору и деструктору я пишу функцию самопроверки, которая просматривает объект и решает, работает он или нет; например, если дескриптор файла равен нулю, но связанные с ним данные файла не обнулены, это указывает на какую-то ошибку (либо дескриптор затерт, либо файл не был открыт, но в этих полях объекта есть мусор). Кроме того, у большинства моих объектов есть поле «подпись», которое должно быть установлено на определенное значение (специфичное для каждого отдельного объекта). Функции, использующие объекты, обычно утверждают, что объекты вменяемы.

Каждый раз, когда у меня malloc() какая-то память, моя функция заполняет память 0xDC значениями. Структура, которая не полностью инициализирована, становится очевидной: счетчики слишком велики, указатели недействительны (0xDCDCDCDC), и когда я смотрю на структуру в отладчике, становится очевидно, что она не инициализирована. Это намного лучше, чем заполнение памяти нулями при вызове malloc(). (Конечно, заливка 0xDC есть только в отладочной сборке; нет необходимости для сборки выпуска тратить это время.)

Каждый раз, когда я освобождаю память, я стираю указатель. Таким образом, если у меня есть глупая ошибка, когда код пытается использовать указатель после того, как его память была освобождена, я мгновенно получаю исключение с нулевым указателем, которое указывает мне прямо на ошибку. Мои функции деструктора не принимают указатель на объект, они принимают указатель на указатель и стирают указатель после разрушения объекта. Кроме того, деструкторы стирают свои объекты перед их освобождением, поэтому, если какой-то фрагмент кода имеет копию указателя и пытается использовать объект, утверждение проверки работоспособности срабатывает мгновенно.

Valgrind сообщит мне, если какой-либо код записывает конец буфера. Если бы у меня этого не было, я бы поставил «канареечные» значения после концов буферов и проверил их при проверке работоспособности. Эти канареечные значения, как и значения подписи, будут предназначены только для отладки, поэтому в версии выпуска не будет раздувания памяти.

У меня есть набор модульных тестов, и когда я вношу какие-либо серьезные изменения в код, запускать модульные тесты очень удобно, и я уверен, что я ничего ужасно не сломал. Конечно, я запускаю модульные тесты как для отладочной, так и для окончательной версии, поэтому у всех моих утверждений есть шанс найти проблемы.

Установка всей этой структуры на место потребовала дополнительных усилий, но они окупаются каждый день. И я чувствую себя вполне счастливым, когда срабатывает assert и указывает мне прямо на ошибку, вместо того, чтобы запускать ошибку в отладчике. В конечном итоге, постоянно содержать вещи в чистоте становится меньше работы.

Наконец, я должен сказать, что мне действительно нравятся венгерские обозначения. Я работал в Microsoft несколько лет назад и, как и Джоэл, выучил Apps венгерский, а не сломанный вариант. Это действительно заставляет неправильный код выглядеть неправильно.

person steveha    schedule 07.10.2009
comment
Все это звучит великолепно ... но я рад, что у меня есть такие люди, как Эрик Липперт, которые устанавливают конструкцию, а я даже пальцем не пошевелил. - person MarkJ; 23.10.2009

Не менее важно - как вы убедиться, что ваши файлы и сокеты закрыты, ваши блокировки сняты, да-а-а. Память - не единственный ресурс, и с GC вы неизбежно теряете надежное / своевременное уничтожение.

Ни сборщик мусора, ни сборщик мусора не превосходят автоматически. У каждого есть свои преимущества, у каждого своя цена, и хороший программист должен уметь справиться с обоими.

Я сказал об этом в ответе на этот вопрос.

person Steve314    schedule 07.10.2009
comment
Существуют методы выполнения RAII на управляемых языках: levelofindirection.com/journal/2009/9/24/ levelofindirection.com/journal/2009/9/24/. - person philsquared; 07.10.2009
comment
... и levelofindirection.com/ journal / 2009/9/24 / - person philsquared; 07.10.2009
comment
@Phil - интересное чтение, но, конечно, любой, кто думает, что это доказывает, что C # и Java превосходит C ++, должен действительно прочитать эти ссылки. Если бы идиома была волшебным лекарством, идиомы для обеспечения правильного удаления объектов, выделенных кучей в C ++, тоже были бы волшебным лекарством, и мы бы не увидели, как любители сборки мусора высмеивают C ++. - person Steve314; 07.10.2009
comment
Розетки и файловые замки - отвлекающий маневр. Для них существуют простые, хорошо отработанные шаблоны в управляемых языках. В C # это оператор using, который автоматически удаляет ресурсы, когда они больше не нужны. - person Robert Harvey; 07.10.2009
comment
@Harvey - не каждый сокет или файл живет только в течение одного вызова функции - и там, где они существуют, локальная переменная C ++, использующая инкапсулированный RAII, чище и менее подвержена ошибкам, чем try / finally. Рассмотрим, например, файлы, лежащие в основе документов приложения GUI, которые вы можете оставить открытыми (например, для блокировки). У вас может быть несколько объектов просмотра, ссылающихся на этот документ. Вы уже имеете дело с проблемами, относящимися как к GC, так и к RAII. В обоих случаях существуют идиомы, обеспечивающие выполнение части работы, но программист должен правильно применять эти идиомы и в целом брать на себя ответственность. - person Steve314; 07.10.2009
comment
Извините - не просто попробуй / наконец-то, а использую тоже и другие идиомы. Подходы на языке GC требуют повторения идиомы для каждого использования класса, который в ней нуждается, тогда как C ++ инкапсулирует RAII в классе раз и навсегда. - person Steve314; 07.10.2009
comment
Я также должен добавить, что try / finally или что-то еще может быть более чистым, когда вам может понадобиться простая локальная переменная, но у вас есть ссылка на объект, выделенный кучей, из какой-то фабрики. Еще лучше, если память - единственный ресурс, который нужно высвободить, конечно. - person Steve314; 07.10.2009
comment
@ Роберт Харви: В стандартном C ++ существуют простые, хорошо зарекомендовавшие себя шаблоны для работы с памятью. На что жалуетесь? В языках все по-другому. Каждый подход в чем-то лучше, в другом - хуже. Оба работают. - person David Thornley; 22.10.2009

Я использую C ++ 10 лет. Я использовал C, Perl, Lisp, Delphi, Visual Basic 6, C #, Java и различные другие языки, которые я не могу припомнить.

Ответ на ваш вопрос прост: вы должны знать, что делаете, больше, чем C # / Java. Больше чем - вот что порождает такие тирады, как Джефф Этвуд, относительно " Школы Java ".

Большинство ваших вопросов в каком-то смысле бессмысленны. «Проблемы», которые вы поднимаете, - это просто факты того, как на самом деле работает оборудование. Я бы хотел попросить вас написать ЦП и ОЗУ на VHDL / Verilog и посмотреть, как все работает на самом деле, даже когда действительно упрощено. Вы начнете понимать, что C # / Java - это абстракция, ограничивающая аппаратное обеспечение.

Более простой задачей было бы запрограммировать простую операционную систему для встроенной системы с момента первого включения; он также покажет вам то, что вам нужно знать.

(Я также написал C # и Java)

person Paul Nathan    schedule 07.10.2009
comment
Задавать вопросы - это часть процесса достижения того, что вы знаете, что делаете. - person Robert Harvey; 07.10.2009
comment
Я не бью тебя, Роберт. Я дал вам лучшее понимание того, как безопасно программировать вне кода виртуальной машины, а также путь к пониманию реальных машин. - person Paul Nathan; 08.10.2009
comment
Я ценю это и тот факт, что c / c ++ часто используется во встроенных системах; очевидно, что он ближе к металлу, чем некоторые другие языки, такие как Java. - person Robert Harvey; 08.10.2009

Мы пишем на C для встраиваемых систем. Помимо использования некоторых методов, общих для любого языка программирования или среды, мы также используем:

  • Инструмент статического анализа (например, PC-Lint).
  • Соответствие MISRA-C (обеспечивается инструментом статического анализа).
  • Нет вообще никакого распределения динамической памяти.
person Steve Melnikoff    schedule 07.10.2009

Эндрю хороший ответ, но я бы также добавил к этому списку дисциплину. Я считаю, что после достаточной практики с C ++ вы довольно хорошо понимаете, что безопасно, а что просит велоцирапторов съесть вы. Вы склонны развивать стиль кодирования, который будет удобен при соблюдении безопасных методов и заставит вас чувствовать себя неловко, если вы попытаетесь, скажем, вернуть умный указатель на необработанный указатель и передать его чему-то еще.

Мне нравится думать об этом как об электроинструменте в магазине. Это достаточно безопасно, если вы научитесь правильно им пользоваться и при условии, что вы всегда соблюдаете все правила безопасности. Вы получаете травму, когда думаете, что можете отказаться от защитных очков.

person Boojum    schedule 07.10.2009

Я работал как на C ++, так и на C #, и я не вижу всей шумихи вокруг управляемого кода.

Да ладно, есть сборщик мусора для памяти, это полезно ... если, конечно, вы не воздержитесь от использования простых старых указателей в C ++, если вы используете только smart_pointers, у вас не так много проблем.

Но тогда я хотел бы знать ... защищает ли ваш сборщик мусора от:

  • держать соединения с базой данных открытыми?
  • блокирует файлы?
  • ...

Управление ресурсами - это гораздо больше, чем управление памятью. Хорошая вещь в C ++ заключается в том, что вы быстро узнаете, что означает управление ресурсами и RAII, так что это становится рефлексом:

  • если мне нужен указатель, я хочу auto_ptr, shared_ptr или weak_ptr
  • если мне нужно соединение с БД, мне нужен объект «Соединение»
  • если я открываю файл, мне нужен объект File
  • ...

Что касается переполнения буфера, ну, мы не везде используем char * и size_t. У нас есть некоторые вещи, называемые 'string', 'iostream' и, конечно же, уже упомянутый метод vector :: at, который освобождает нас от этих ограничений.

Протестированные библиотеки (stl, boost) хороши, используйте их и решайте более функциональные задачи.

person Matthieu M.    schedule 07.10.2009
comment
Подключения к базе данных и блокировки файлов - отвлекающий маневр. Для них существуют простые, хорошо отработанные шаблоны в управляемых языках. В C # это оператор using, который автоматически удаляет ресурсы, когда они больше не нужны. - person Robert Harvey; 07.10.2009
comment
ИМО, основная проблема интеллектуальных указателей в C ++ заключается в том, что нет настоящего стандарта. Если вы используете сторонние библиотеки / фреймворки, маловероятно, что все они будут использовать один и тот же тип интеллектуального указателя. Таким образом, вы можете положиться на них в модуле, но как только вы будете взаимодействовать с компонентами от разных поставщиков, вы вернетесь к ручному управлению памятью. - person Niki; 07.10.2009
comment
@nikie: когда я использую сторонние компоненты, я ожидаю, что они будут очень четко понимать свою стратегию управления памятью. Но с другой стороны, единственными третьими библиотеками, которые у нас есть в работе, являются OpenSource, такие как Boost или Cyptopp, поэтому у меня нет большого опыта в этом. - person Matthieu M.; 08.10.2009

Помимо множества полезных советов, приведенных здесь, мой самый важный инструмент - DRY - Don't Repeat Yourself. Я не распространяю код, подверженный ошибкам (например, для обработки выделения памяти с помощью malloc () и free ()) по всей моей кодовой базе. В моем коде есть ровно одно место, где вызываются malloc и free. Он находится в функциях-оболочках MemoryAlloc и MemoryFree.

Есть вся проверка аргументов и начальная обработка ошибок, которая обычно дается как повторяющийся шаблонный код вокруг вызова malloc. Кроме того, он позволяет все, что требует изменения только одного местоположения, начиная с простых проверок отладки, таких как подсчет успешных вызовов malloc и free и проверка при завершении программы, что оба числа равны, вплоть до всевозможных расширенных проверок безопасности.

Иногда, когда я читаю здесь вопрос вроде «Мне всегда нужно убедиться, что strncpy завершает строку, есть ли альтернатива?»

strncpy(dst, src, n);
dst[n-1] = '\0';

с последующими днями дискуссий я всегда задавался вопросом, является ли искусство извлечения повторяющейся функциональности в функции утерянным искусством высшего программирования, которому больше не преподают на лекциях по программированию.

char *my_strncpy (dst, src, n)
{
    assert((dst != NULL) && (src != NULL) && (n > 0));
    strncpy(dst, src, n);
    dst[n-1] = '\0';
    return dst;
}

Основная проблема дублирования кода решена - теперь давайте подумаем, действительно ли strncpy подходит для работы. Представление? Преждевременная оптимизация! И одно-единственное место, чтобы начать с него после того, как оно окажется узким местом.

person Secure    schedule 07.10.2009

В C ++ есть все упомянутые вами функции.

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

C ++ - это строго типизированный язык. Прямо как C #.

Мы используем буферы. Вы можете использовать версию интерфейса с проверкой границ. Но если вы знаете, что это не проблема, вы можете использовать непроверенную версию интерфейса.

Сравнить метод at () (отмечен) с оператором [] (не отмечен).

Да, мы используем модульное тестирование. Точно так же, как вы должны использовать в C #.

Да, мы осторожные кодеры. Точно так же, как вы должны быть в C #. Единственная разница в том, что подводные камни в двух языках разные.

person Martin York    schedule 07.10.2009
comment
Я не видел вопроса, имеет ли C ++ современные преимущества управления памятью, но если вы программируете на C ++, без современных преимуществ управления памятью, ..., как вы убедитесь, что что ваши программы безопасны? - person Pete Kirkham; 07.10.2009
comment
Если я программирую без интеллектуальных указателей, намного сложнее убедиться, что мои программы безопасны. Однако я не вижу в этом актуальности. Если вы программируете на C # без использования оператора using (IIRC - довольно недавнее дополнение), как вы убедитесь, что другие ваши ресурсы расположены правильно? - person David Thornley; 22.10.2009
comment
Разве умные указатели не подходят в тех же ситуациях, когда подсчет ссылок VB6 и COM был адекватен? Это то, что Microsoft хотела улучшить, когда они выбрали стиль .NET для сборки мусора. - person MarkJ; 23.10.2009
comment
@MarkJ: Вряд ли. Подсчет ссылок COM возлагает ответственность на пользователя. Интеллектуальный указатель, такой как сборщик мусора, возлагает ответственность на разработчика интеллектуального указателя / сборщика мусора. По сути, Smart Pointers - это более детализированная детерминированная сборка мусора (в отличие от GC, которая не является детерминированной). - person Martin York; 23.10.2009
comment
@MarkJ: В Java GC добавляет столько других проблем, что деструкторы (или финализаторы практически бесполезны), в то время как в .NET им пришлось добавить концепцию использования, чтобы сделать сборку мусора пригодной для использования. Итак, реальный вопрос заключается в том, почему вы думаете, что использование cocept лучше, чем умные указатели, когда использование возвращает ответственность пользователю объекта, как это делал подсчет ссылок COM. - person Martin York; 23.10.2009
comment
Прочтите это: stackoverflow.com/questions/1064325/ для более подробного описания интеллектуальных указателей и их преимуществ GC. - person Martin York; 23.10.2009