Вопрос

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

Если я создам такую ​​структуру:

typedef struct Cell {
  struct Cell* subcells;
} 

а затем позже инициализируйте его вот так

Cell makeCell(int dim) {
  Cell newCell;

  for(int i = 0; i < dim; i++) {
    newCell.subcells[i] = makeCell(dim -1);
  }

  return newCell; //ha ha ha, this is here in my program don't worry!
}

Собираюсь ли я в конечном итоге получить доступ к счастливым лицам, хранящимся где-то в памяти, или, возможно, переписать ранее существующие ячейки или что-то еще?Мой вопрос: как C выделяет память, если я на самом деле не выполнил malloc() соответствующий объем памяти?Что по умолчанию?

Это было полезно?

Решение

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

newCell.subcells[i] = ...

Эффективно получает доступ к некоторой неопределенной части памяти.Помните, что subcells[i] эквивалентно

*(newCell.subcells + i)

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

newCell.subcells = malloc(bytecount)

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

  • malloc возвращает указатель на объект без типа.Вы можете указать указатель на эту область памяти, и тип объекта фактически станет типом типа объекта, на который указывает указатель.Память не инициализируется каким-либо значением, и доступ обычно происходит медленнее.Полученные таким образом объекты называются allocated objects.
  • Вы можете размещать объекты глобально.Их память будет инициализирована нулями.Для точек вы получите NULL-указатели, для чисел с плавающей запятой вы также получите правильный ноль.Вы можете положиться на правильное начальное значение.
  • Если у вас есть локальные переменные, но вы используете static спецификатор класса хранения, то у вас будет то же правило начального значения, что и для глобальных объектов.Память обычно распределяется так же, как и глобальные объекты, но это ни в коем случае не является необходимостью.
  • Если у вас есть локальные переменные без какого-либо спецификатора класса хранения или с auto, то ваша переменная будет размещена в стеке (даже если она не определена в C, это, конечно, практически делают компиляторы).Вы можете взять его адрес, и в этом случае компилятору, конечно, придется пропустить оптимизацию, например, помещение его в регистры.
  • Локальные переменные, используемые со спецификатором класса хранения register, помечены как имеющие специальное хранилище.В результате вы больше не сможете получить его адрес.В последних компиляторах обычно нет необходимости использовать register больше из-за их сложных оптимизаторов.Однако если вы действительно эксперт, то, используя его, вы можете получить некоторую производительность.

Объекты имеют связанную продолжительность хранения, которую можно использовать для отображения различных правил инициализации (формально они определяют только то, как долго живут объекты).Объекты, объявленные с помощью auto и register имеют автоматическую продолжительность хранения и являются нет инициализирован.Вам необходимо явно инициализировать их, если вы хотите, чтобы они содержали какое-то значение.Если вы этого не сделаете, они будут содержать все, что компилятор оставил в стеке до начала своего существования.Объекты, которые выделяются malloc (или другая функция этого семейства, например calloc) выделили продолжительность хранения.Их хранение нет инициализируется либо.Исключением является использование calloc, и в этом случае память инициализируется нулем («настоящий» ноль.то есть все байты 0x00, независимо от представления нулевого указателя).Объекты, объявленные с помощью static а глобальные переменные имеют статическую продолжительность хранения.Их хранение является инициализируются нулем, соответствующим их соответствующему типу.Обратите внимание, что объект не должен иметь типа, но единственный способ получить объект без типа — использовать выделенное хранилище.(Объект в C — это «область хранения»).

Так что есть что?Вот фиксированный код.Поскольку после того, как вы выделили блок памяти, вы больше не можете получить обратно количество выделенных элементов, лучше всего всегда где-то хранить это количество.Я ввел переменную dim в структуру, которая сохраняет счетчик.

Cell makeCell(int dim) {
  /* automatic storage duration => need to init manually */
  Cell newCell;

  /* note that in case dim is zero, we can either get NULL or a 
   * unique non-null value back from malloc. This depends on the
   * implementation. */
  newCell.subcells = malloc(dim * sizeof(*newCell.subcells));
  newCell.dim = dim;

  /* the following can be used as a check for an out-of-memory 
   * situation:
   * if(newCell.subcells == NULL && dim > 0) ... */
  for(int i = 0; i < dim; i++) {
    newCell.subcells[i] = makeCell(dim - 1);
  }

  return newCell;
}

Теперь для dim=2 все выглядит так:

Cell { 
  subcells => { 
    Cell { 
      subcells => { 
        Cell { subcells => {}, dim = 0 }
      }, 
      dim = 1
    },
    Cell { 
      subcells => { 
        Cell { subcells => {}, dim = 0 }
      }, 
      dim = 1
    }
  },
  dim = 2
}

Обратите внимание, что в C возвращаемое значение функции не обязательно должно быть объектом.Для существования вообще не требуется никакого хранилища.Следовательно, вы не имеете права его изменить.Например, невозможно следующее:

makeCells(0).dim++

Вам понадобится «свободная функция», которая снова освободит выделенную память.Потому что память для выделенных объектов не освобождается автоматически.Вы должны позвонить free освободить эту память для каждого subcells указатель в вашем дереве.Это оставлено вам в качестве упражнения, чтобы написать это :)

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

Короткий ответ: Вам оно не выделено.

Чуть более длинный ответ: А subcells указатель не инициализирован и может указывать в любом месте.Это ошибка и ты никогда не должен позволять этому случиться.

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

А newCell переменная в вашей функции является автоматической и не инициализируется.Вам следует это исправить как можно скорее.Либо дайте newCell.subcells быстро указать значимое значение или указать на него NULL пока вы не выделите для него немного места.Таким образом, вы выдадите нарушение сегментации, если попытаетесь разыменовать его, прежде чем выделить для него немного памяти.

Хуже того, вы возвращаете Cell по значению, но присваивая его Cell * когда вы пытаетесь заполнить subcells множество.Либо верните указатель на объект, выделенный в куче, либо присвойте значение локально выделенному объекту.

Обычная идиома для этого будет иметь что-то вроде

Cell* makeCell(dim){
  Cell *newCell = malloc(sizeof(Cell));
  // error checking here
  newCell->subcells = malloc(sizeof(Cell*)*dim); // what if dim=0?
  // more error checking
  for (int i=0; i<dim; ++i){
    newCell->subCells[i] = makeCell(dim-1);
    // what error checking do you need here? 
    // depends on your other error checking...
  }
  return newCell;
}

хотя я оставил вам несколько проблем, которые нужно решить..

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

Все, что не выделено в куче (через malloc и подобные вызовы) вместо этого выделяется в стеке.По этой причине все, созданное в определенной функции, не будучи malloc'd будет уничтожен после завершения функции.Сюда входят возвращенные объекты;когда стек разматывается после вызова функции, возвращаемый объект копируется в пространство, выделенное для него в стеке вызывающей функцией.

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

Мой вопрос: как C выделяет память, если я на самом деле не выполнил malloc() соответствующий объем памяти?Что по умолчанию?

Чтобы не выделять память.Вы должны явно создать его в стеке или динамически.

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

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

Вам повезло, что у вас счастливое лицо.В один из таких неудачных дней он мог полностью стереть вашу систему ;)

Мой вопрос: как C выделяет память, если я на самом деле не выполнил malloc() соответствующий объем памяти?

Это не так.Однако происходит следующее: когда вы определяете Cell newCell, указатель subCells инициализируется значением мусора.Это может быть 0 (в этом случае произойдет сбой) или какое-то целое число, достаточно большое, чтобы оно выглядело как реальный адрес памяти.В таких случаях компилятор с радостью извлечет любое значение, находящееся там, и вернет его вам.

Что по умолчанию?

Это тот поведение, если вы не инициализируете свои переменные.И ваш makeCell функция выглядит немного недостаточно развитой.

На самом деле есть три раздела, в которых можно выделить данные: данные, стек и куча.

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

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

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

Локальные переменные «распределяются» в стеке.Стек — это заранее выделенный объем памяти для хранения этих локальных переменных.Переменные перестают быть действительными при выходе из функции и будут перезаписаны тем, что произойдет дальше.

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

Я собираюсь представить, что я здесь компьютер и читаю этот код...

typedef struct Cell {
  struct Cell* subcells;
}

Это говорит мне:

  • У нас есть тип структуры под названием Cell.
  • Он содержит указатель, называемый подячейками.
  • Указатель должен быть на что-то типа struct Cell.

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

Cell makeCell(int dim) {
  Cell newCell;

Новая структура Cell с неопределенным указателем подячеек.Все, что делается, — это резервируется небольшой участок памяти под названием newCell, который имеет размер структуры Cell.Это не меняет значений, которые были в этой памяти — они могут быть какими угодно.

  for(int i = 0; i < dim; i++) {
    newCell.subcells[i] = makeCell(dim -1);

Чтобы получить newCell.subcells[i], производится вычисление смещения от подячеек на i, тогда это разыменован.В частности, это означает, что значение извлекается из этого адреса памяти.Возьмем, к примеру, i==0...Тогда мы будем разыменовывать сам указатель подячеек (без смещения).Поскольку подячейки не определены, это может быть что угодно.Буквально что угодно!Таким образом, это потребует значения из совершенно случайного места в памяти.Никаких гарантий на результат нет.Он может что-то напечатать, может произойти сбой.Этого определенно делать не следует.

  }

  return newCell;
}

Каждый раз, когда вы работаете с указателем, важно убедиться, что ему присвоено значение, прежде чем разыменовывать его.Попросите свой компилятор выдавать вам любые предупреждения, многие современные компиляторы могут уловить подобные вещи.Вы также можете присвоить указателям забавные значения по умолчанию, например 0xdeadbeef (ага!это число в шестнадцатеричном формате, это просто тоже слово, поэтому выглядит забавно) чтобы они выделялись.(Опция %p для printf полезна для отображения указателей в качестве грубой формы отладки.Программы-отладчики также могут их довольно хорошо показать.)

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top