Почему я могу писать и читать память, если у меня нет выделенного пространства? - PullRequest
1 голос
/ 25 февраля 2010

Я пытаюсь создать свой собственный Hash Table в C с нуля как упражнение, и я делаю один маленький шаг за раз. Но у меня небольшая проблема ...

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

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

Вот мой текущий код:

#include <stdio.h>
#include <stdlib.h>


#define HASHSIZE 2


typedef char *HashKey;
typedef int HashValue;

typedef struct sHashTable {
    HashKey key;
    HashValue value;
} HashEntry;

typedef HashEntry *HashTable;


void hashInsert(HashTable table, HashKey key, HashValue value) {
}

void hashInitialize(HashTable *table, int tabSize) {
    *table = malloc(sizeof(HashEntry) * tabSize);

    if(!*table) {
        perror("malloc");
        exit(1);
    }

    (*table)[0].key = "ABC";
    (*table)[0].value = 45;
    (*table)[1].key = "XYZ";
    (*table)[1].value = 82;
    (*table)[2].key = "JKL";
    (*table)[2].value = 13;
}


int main(void) {
    HashTable t1 = NULL;

    hashInitialize(&t1, HASHSIZE);

    printf("PAIR(%d): %s, %d\n", 0, t1[0].key, t1[0].value);
    printf("PAIR(%d): %s, %d\n", 1, t1[1].key, t1[1].value);
    printf("PAIR(%d): %s, %d\n", 3, t1[2].key, t1[2].value);
    printf("PAIR(%d): %s, %d\n", 3, t1[3].key, t1[3].value);

    return 0;
}

Вы можете легко увидеть, что я не выделил место ни для (*table)[2].key = "JKL";, ни (*table)[2].value = 13;. Я также не должен быть в состоянии прочитать ячейки памяти за последние 2 printfs в main().

Может кто-нибудь, пожалуйста, объясните мне это, и если я могу / должен что-нибудь с этим сделать?

EDIT:
Хорошо, я понял кое-что о моем коде выше, который беспорядок ... Но у меня есть класс прямо сейчас, и я не могу обновить свой вопрос. Я обновлю это, когда у меня будет время. Извините за это.

РЕДАКТИРОВАТЬ 2:
Извините, но я не должен был публиковать этот вопрос, потому что я не хочу, чтобы мой код, как я опубликовал выше. Я хочу сделать что-то немного другое, что делает этот вопрос немного неуместным. Итак, я просто собираюсь предположить, что это был вопрос, на который мне нужен был ответ, и принять один из правильных ответов ниже. Затем я опубликую свои правильные вопросы ...

Ответы [ 5 ]

7 голосов
/ 25 февраля 2010

Только не делайте этого, это неопределенное поведение.

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

Только не делайте этого - только чтение / запись памяти, которую вы законно выделили.

2 голосов
/ 25 февраля 2010

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

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

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

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

Один из способов понять это через этот грубый пример: когда вы запрашиваете 1 байт в пространстве пользователя,Ядро должно выделять целую страницу по крайней мере (например, 4 КБ в некоторых системах Linux - самое детальное распределение на уровне ядра).Чтобы повысить эффективность за счет сокращения частых вызовов, ядро ​​назначает всю эту страницу вызывающей библиотеке, которую библиотека может распределять, например, при поступлении большего количества запросов. Таким образом, запросы на запись или чтение в такой регион могут необязательногенерировать ошибку.Это просто означало бы мусор.

1 голос
/ 25 февраля 2010

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

На практике ОС выделяет объем памяти процесса в виде кусков (страниц) обычно 8 КБ (но это зависит от ОС). Затем библиотека C управляет этими страницами и поддерживает списки того, что свободно и что выделено, предоставляя пользовательские адреса этих блоков при запросе с помощью malloc.

Таким образом, когда вы возвращаете указатель из malloc (), вы указываете на область на странице 8k, которая доступна для чтения и записи. Эта область может содержать мусор, или она может содержать другую память malloc, она может содержать память, используемую для переменных стека, или она даже может содержать память, используемую библиотекой C для управления списками свободной / выделенной памяти!

Таким образом, вы можете себе представить, что запись по адресам за пределами диапазона, который вы указали в malloc, действительно может вызвать проблемы:

  • Повреждение других недопустимых данных
  • Повреждение стековых переменных или самого стека вызовов, приводящее к сбоям, когда функция возвращает
  • Повреждение памяти управления malloc / free библиотеки C, приводящее к сбоям при вызове malloc () или free ()

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

Только когда вы читаете или пишете с / на адрес, который не соответствует отображенной странице, вы получите сбой ... например, чтение с адреса 0x0 (NULL)

Malloc, Free и указатели очень хрупки в C (и в несколько меньшей степени в C ++), и очень легко случайно выстрелить себе в ногу

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

0 голосов
/ 19 ноября 2012

Думайте о памяти как о большой большой доске, разделенной на маленькие квадраты. Запись в ячейку памяти эквивалентна стиранию квадрата и записи туда нового значения. Цель malloc обычно не состоит в том, чтобы создать память (квадраты классной доски); скорее, это для определения области памяти (группы квадратов), которая не используется ни для чего другого, и предпринять некоторые действия, чтобы гарантировать, что она не будет использоваться ни для чего другого до дальнейшего уведомления. Исторически сложилось так, что микропроцессоры довольно часто выставляли приложению всю системную память. Кусок кода Foo теоретически может выбрать произвольный адрес и сохранить там свои данные, но с несколькими основными оговорками:

  1. Какой-то другой код `Bar` мог ранее хранить что-то там с ожиданием того, что оно останется. Если `Bar` читает это местоположение, ожидая получить обратно то, что он написал, он будет ошибочно интерпретировать значение, записанное` Foo`, как его собственное. Например, если в «Bar» сохранено количество полученных виджетов (23), а в «Foo» сохранено значение 57, более ранний код будет считать, что он получил 57 виджетов.
  2. Если `Foo` ожидает, что данные, которые он записывает, будут оставаться в течение какого-то значительного промежутка времени, его данные могут быть перезаписаны каким-либо другим кодом (в основном оборотная сторона вышеописанного).

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

0 голосов
/ 25 февраля 2010

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

Но все же не стоит этого делать . Поскольку память, в которую вы пишете, может считаться незанятой или фактически занятой другими, может произойти все, например. 2-я и 3-я пара ключ / значение станут мусором позже или неактуальная жизненно важная функция выйдет из строя из-за недопустимых данных, которые вы поместили в его malloc -данную память.

(Также используйте либо char[≥4] в качестве типа ключа, либо malloc ключ, потому что, если ключ, к сожалению, хранится в стеке, он станет недействительным позже.)

...