Почему появляется ошибка памяти с небезопасным кодом в С#

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

byte count = 100;
byte* pointer = &count;
(*(ushort*)pointer) = (ushort)byte.MaxValue;

Я нашел ошибку в третьей строке и исправил ее. *pointer = byte.MaxValue;

Но я точно не понимаю, почему код ошибки иногда запускается без ошибки, а иногда завершается с ошибкой:

malloc.c:2379: sysmalloc: Assertion `(old_top == initial_top (av) && old_size == 0) || ((unsigned long) (old_size) >= MINSIZE && prev_inuse (old_top) && ((unsigned long) old_end & (pagesize - 1)) == 0)' failed.

Я вижу, что инструкция (*(ushort*)pointer) = (ushort)byte.MaxValue; записывает два байта в память, и вторая ячейка памяти здесь может использоваться для других переменных текущего процесса или других процессов.

Почему иногда ошибку не выдает?

Может быть, он работает нормально, если второй байт «свободен»?

Или, может быть, он выдает только в том случае, если второй байт принадлежит другому процессу и относится к другому адресному пространству?

Зачем вы вообще возитесь с необработанными указателями, каков ваш вариант использования?

Charlieface 08.08.2024 12:48
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
1
51
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

(*(ushort*)pointer) = (ushort)byte.MaxValue; пытается записать 2 байта с помощью указателя, который на самом деле указывает на 1 байт.

Результатом является неопределенное поведение.

Пример здесь для неопределенного поведения (в разделе 23.5 Преобразования указателей, 23.5.1 Общие сведения) демонстрирует аналогичный случай:

unsafe static void M()
{
    char c = 'A';
    char* pc = &c;
    void* pv = pc;
    int* pi = (int*)pv; // pretend a 16-bit char is a 32-bit int
    int i = *pi;        // read 32-bit int; undefined
    *pi = 123456;       // write 32-bit int; undefined
}

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

Касательно:

Или, может быть, он выдает, только если второй байт принадлежит другому процессу и это относится к другому адресному пространству?

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

небольшое уточнение. Разве в неуправляемом коде невозможно получить доступ к ячейке памяти адресного пространства другого процесса? Я не могу присвоить указателю произвольный адрес (который будет «внешним» адресом) и прочитать/записать из него значение?

vitm 08.08.2024 11:35

@vitm, да, таким образом невозможно. ОС позволяет вам получить доступ только к текущему адресному пространству памяти процесса (в некоторых случаях существуют специальные API Win32 для доступа к другому адресному пространству, но это не то, что вы можете сделать тривиально).

wohlstad 08.08.2024 11:38

Добавил дополнительную информацию об этом в свой ответ.

wohlstad 08.08.2024 11:50

@wohlstad, а технически, если я укажу из кода на ячейку случайной памяти другого процесса, мой процесс выйдет из строя? А из примера кода этот второй байт может относиться к другому адресному пространству?

vitm 08.08.2024 12:08

Нет, вы просто не можете этого сделать технически. Любой адрес, который вы можете иметь в указателе, всегда находится в вашем адресном пространстве. ОС сохраняет полное разделение между адресными пространствами. Адрес X в одном процессе полностью отличается от того же адреса X в другом (даже если X — тот же номер).

wohlstad 08.08.2024 12:17

@vitm Поиск термина «виртуальная память», который вы описываете, совершенно невозможен в обычном коде пользовательского режима (не в ядре/драйверах)

Charlieface 08.08.2024 12:48

@vitm это поможет вам начать работу, если вы используете Windows: Виртуальное адресное пространство (управление памятью.

wohlstad 08.08.2024 12:51

Другие вопросы по теме