Срок хранения составных литералов

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

давайте предположим, что составной литерал определен в функции или блоке, который вызывается повторно; Когда эта функция вызывается в первый раз, как компьютер может создать литерал, если он не находится в статической памяти для начала?? (что я имею в виду, как он знает его значение? например, это (int [2]) {2,4} или (int [5]) {5,4,2,1,4}) и если это как-то существует где-нибудь, как компьютер снова узнает его содержимое? (когда он пытается построить его снова для второго и каждого последующего вызова) после его первого прохождения.

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

кто-нибудь может объяснить это мне правильно??

3 ответа

Решение

Возможно, называть объект "составным литералом" немного обманчиво. На самом деле это не так буквально.

Полезно рассмотреть реальный пример, даже если он немного тупой:

/* This is NOT the way to solve this problem */
double cubic(double x, double a, double b, double c, double d) {
  double* powers = (double []){1, x, x*x, x*x*x};
  double* coefficients = (double []){a, b, c, d};
  double sum = 0;
  for (int i = 0; i < 4; ++i) sum += powers[i]*coefficients[i];
  return sum;
}

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

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

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

double cubic(double x, double a, double b, double c, double d) {
  double _powers_values_[4];
  _powers_values_[0] = 1;
  _powers_values_[1] = x;
  _powers_values_[2] = x*x;
  _powers_values_[3] = x*x*x;
  double* powers = _powers_values_;
  // ...

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

Также обратите внимание, что оба powers а также coefficients являются изменяемыми, поэтому я мог бы изменить их в функции:

/* This is NOT the way to solve this problem */
double cubic(double x, double a, double b, double c, double d) {
  double* powers = (double []){1, x, x*x, x*x*x};
  double* coefficients = (double []){a, b, c, d};
  for (int i = 0; i < 4; ++i) coefficients[i] *= powers[i];
  for (int i = 1; i < 4; ++i) coefficients[i] += coefficients[i+1];
  return coefficients[3];
}

Конечно, составной литерал может просто иметь постоянные значения:

double* coefficients = (double []){17, 6, -3, 2.5};

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

const double* coefficients = (const double []){17, 6, -3, 2.5};

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

Ваш вопрос не объяснен должным образом. Если вы хотите узнать о области применения составных литералов, то стандарт говорит, что:

C11: 6.5.2.5 Составные литералы (P5):

[...] Если составной литерал находится вне тела функции, объект имеет статическую продолжительность хранения; в противном случае он имеет автоматическую продолжительность хранения, связанную с окружающим блоком.

Инициализация составными литералами выполняется следующим образом:

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

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

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

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

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