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

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

9 ответов


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

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

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

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


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

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

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

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

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

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

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

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

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

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

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


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

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

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


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

ответ на ваш вопрос прост: вы должны знать, что вы делаете, больше, чем C#/Java. The больше, чем это то, что порождает такие разглагольствования, как Джефф Этвуд в отношении "Java Schools".

большинство ваших вопросов, в некотором смысле, это ерунда. Этот 'проблемы' вы подняли просто факты как оборудование действительно работает. Я хотел бы предложить вам написать CPU & RAM в VHDL / Verilog и посмотреть, как все работает, даже когда действительно упрощенный. Вы начнете понимать, что способ C# / Java-это абстракция, оклеивающая аппаратное обеспечение.

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

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


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

  • инструмент статического анализа (например,PC-Lint).
  • соответствие МИСРА-С (применяется инструментом статического анализа).
  • нет динамического выделения памяти вообще.

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


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

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

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

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

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

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

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

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


помимо многих хороших советов, приведенных здесь, мой самый важный инструмент сухой - не повторяйтесь. Я не распространяю код, подверженный ошибкам (например, для обработки выделений памяти с помощью malloc() и free()) по всей моей кодовой базе. У меня есть ровно одно место в моем коде, где называются malloc и free. Именно в оболочке функции MemoryAlloc и MemoryFree.

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

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

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

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

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

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


C++ имеет все функции, которые вы упоминаете.

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

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

мы используем буферы. Вы можете использовать проверенную версию интерфейса bounds. Но если ты знаешь, что есть не проблема, тогда вы можете использовать непроверенную версию интерфейса.

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

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

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