Последствия этих ошибок указателей в C ++ и Managed - PullRequest
0 голосов
/ 06 февраля 2009

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

Edit2: Я занимаюсь рефакторингом этого вопроса. Различие, которое я пытаюсь провести, состоит в том, что в управляемом коде все эти ошибки единообразно обрабатываются с помощью исключения. Тем не менее, C ++ не так прост - и я хочу понять, есть ли в каждом случае вероятность ошибки, segfault, восстанавливаемого поведения или, что еще хуже, ошибки silent , которая распространяется , Пожалуйста, посмотрите мои новые конкретные примеры (и да, я знаю, что ответ всегда «именно так, как он закодирован»; в конце концов, я программист. Я хочу знать интересные детали того, с чем вы часто сталкиваетесь.)

Edit3: В дальнейшем под "классом" я имею в виду экземпляр класса. Спасибо

Ошибка 1: Значение указателя NULL, он же указатель == 0

  • Управляемый код: создает исключение NullPointerException во время выполнения
  • C ++:?
  • Пример: Ну, да, у вас есть указатель на класс , но он инициализируется равным 0. Что происходит, когда вы отправляете его функции. то есть. C ++ не оставляет никаких указаний на класс; это просто объединение публичных "заполнителей".

Ошибка 2: Указатель указывает на прежний класс в памяти, значение которого равно NULL или == 0

  • Управляемый код: не разрешен для модели памяти. Все упомянутые объекты остаются в памяти. Нет исключительных случаев?
  • C ++:?
  • Пример: у вас был указатель на класс , и класс был удален. Затем вы передаете указатель в качестве аргумента функции. Очевидно, что проблема, которая возникает, зависит от того, как функция работает с указанным классом. Мой вопрос: есть ли отказоустойчивая обработка для этого на STL? Хорошая фирменная библиотека? Средний открытый исходный код?

Ошибка 3: указатель указывает на класс, который не является правильным классом или подклассом

  • Управляемый код: выдает исключение ClassCastException.
  • C ++: [исправить, если не так] Компилятор пытается бороться с этим, не допуская неудачных приведений. Однако, если это произойдет во время выполнения, я предполагаю неопределенное поведение. Существуют ли случаи подобных объектов класса, где это не всегда взрывается?
  • Пример: ваш указатель переназначен неправильно, чтобы его значение было полностью равно другому классу. Я предполагаю, что функция, которой вы передаете этот ссылочный класс, просто слепо захватит смещение любых переменных экземпляра, на которые она ссылается. Таким образом, он неправильно интерпретирует необработанный двоичный файл. Нет способа предотвратить это в C ++? И / или ... есть ли случай, когда эта способность используется навсегда?

Ошибка 4: указатель указывает на середину класса (выровненный) или неинициализированный мусор

  • Управляемый код: не разрешен моделью памяти.
  • C ++: эквивалентно случаю 3?
  • Пример: часто вы действительно используете это на законных основаниях. Например, вы можете получить доступ к массиву вектора STL напрямую - это указывает на середину класса. Тем не менее, кажется, что так же легко "пропустить"? Есть ли распространенная ловушка, в которой это может произойти против вашей воли, например, если загружена библиотека, отличная от той, с которой вы связаны (и есть ли механизм, предотвращающий это?)

Заранее спасибо всем участникам.

Ответы [ 8 ]

1 голос
/ 06 февраля 2009
  1. Значение указателя равно NULL, иначе указатель == 0 Неопределенное поведение. Компилятору разрешено делать все, что он хочет, в том числе каждый раз что-то другое. В большинстве систем на основе Unix это вызовет ошибку сегментации.
  2. Доступ к удаленному указателю Это неопределенное поведение. В некоторых случаях, в зависимости от точных схем распределения и использования памяти, вы можете использовать удаленный указатель, как если бы он не был удален, если память не была использована повторно для чего-то другого. Это может привести к очень трудно выявлять ошибки. Если вы удалите указатель во второй раз, вы, вероятно, повредите систему выделения памяти, что приведет к сбою совершенно не связанных новостей / удалений
  3. Указатель указывает на класс, который не относится к правильному классу или подклассу C ++ не выполняет проверку типов во время выполнения. Он попытается интерпретировать область памяти как тип указателя. Если объект правильного типа не был создан в этом месте, это неопределенное поведение, и может произойти любой (включая то, что он работает правильно).
  4. Указатель указывает на середину класса (выровненный) или неинициализированный мусор То же, что и выше, неопределенное поведение.

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

1 голос
/ 06 февраля 2009

Хорошо. В C ++ разыменование указателя в любом случае, кроме случая 2, приведет к неопределенному поведению, поэтому вы не знаете, что происходит. Однако для большинства операционных систем разыменование нулевого указателя вызовет ошибку сегментации.

Простое использование указателя в сравнениях нормально для нулевого указателя, но точно не определено (не указано) для любого другого случая, кроме этого и случая 2.

Случай 2 прекрасно определен. Вы можете указать указатель на int со значением 0. Я не понимаю, почему такая вещь была бы недопустимой даже в C #. Возможно, я неправильно понял ваш случай 2

В случае 3 вы должны различать, указывает ли указатель уже на этот неправильный объект, или вы все еще пытаетесь указать на него. C ++ dynamic_cast проверит тип объекта, на который вы указываете, и если он не является производным или того же типа, что и приведенный вами тип, то он даст вам нулевой указатель. Но есть другие приведения, которые не выполняют эту проверку и оставят вас с неверным указателем.

1 голос
/ 06 февраля 2009

Большинство из них вызывают непредсказуемое поведение. Цитируя Стив Макконнелл, Code Complete 2 Edition, «Использование указателей по своей сути сложно, и для их правильного использования необходимо, чтобы вы прекрасно понимали схему управления памятью вашего компилятора».

0 голосов
/ 06 февраля 2009

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

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

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

Другие способы выучить этот материал:

  • Пройдите аудит класса по построению компилятора.
  • Создайте что-нибудь интересное с помощью контроллера PIC - робота или калькулятора.
0 голосов
/ 06 февраля 2009

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

Взять, к примеру, пункт 3, это техника, используемая во многих атаках на ядро. Узнайте, где находится ядро, и используйте указатели для изменения информации. Я ни в коем случае не предлагаю, чтобы кто-то попробовал это, я не потворствую использованию руткитов или любого другого вредоносного ПО.

0 голосов
/ 06 февраля 2009

Это будет моя исправленная версия ваших ошибок:

Ошибка 1: нулевой указатель / ссылка

  • Управляемый код: генерирует исключение NullReferenceException, если оно является ссылкой, или AccessViolationException, если это указатель (да! В управляемом коде существуют указатели!)
  • Собственный код: в Windows это вызывает «нарушение прав доступа» (часто называемое AV). В Unix это будет называться «ошибка сегмента». В Windows это теоретически можно отследить с помощью обработки исключений

Ошибка 2: указатель на освобожденный объект

  • Управляемый код: обычно не определено, но очень вероятно AccessViolationException. (Обратите внимание, что это относится к фактическому использованию указателя, не управляемые ссылки, которые будут всегда будет действительным)
  • Собственный код: Обычно не определено, но, вероятно, Нарушение прав доступа.

Ошибка 3:

  • Управляемый код: исключение
  • Родной код: в зависимости от типа актерского состава, будет ли ошибка компилятора, если статическое приведение или неопределенный результат если переосмысление приведено.

Ошибка 4:

  • Управляемый код: не определено
  • Собственный код: Undefined
0 голосов
/ 06 февраля 2009

В Windows Ошибка 1 приведет к возникновению структурного исключения (win32) для нарушения прав доступа при попытке доступа к странице виртуальной памяти, к которой у вас нет прав на чтение. Производные от Unix ОС имеют похожий механизм, хотя и с другой терминологией.

Это хорошо определенное (если обычно нежелательное!) Поведение, которое может быть перехвачено структурированным обработчиком исключений. Обычно управляемая среда выполнения полагается на базовую ОС, которая вызывает это исключение, а затем обрабатывает его и преобразует в управляемое исключение. Это гораздо эффективнее, чем проверка каждого указателя на наличие нуля, прежде чем следовать за ним.

0 голосов
/ 06 февраля 2009

# 1 должен бросить segfault.

# 2, # 3 и # 4 могут работать, в зависимости от того, что пытается сделать метод. Помните, что в C ++ код класса хранится только один раз (и отдельно от данных экземпляра, на которые ссылаются указатели объектов), поэтому можно вызывать методы класса на случайных порциях памяти. Например, следующие отпечатки «-1» (протестировано с g ++ 4):

#include <iostream>

class Foo
{
public:
    int x;
    void foo()
    {
        std::cout << x << std::endl;
    }
};

int main(void)
{
    void* mem = malloc(1024);
    memset(mem, 0xff, 1024);
    Foo* myFoo = (Foo*)mem;
    myFoo->foo();
    return 0;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...