Срок хранения составных литералов
Я почему-то не могу понять, как длительность хранения составных литералов, определенных в блоках, автоматическая, и причина заключается в следующем:
давайте предположим, что составной литерал определен в функции или блоке, который вызывается повторно; Когда эта функция вызывается в первый раз, как компьютер может создать литерал, если он не находится в статической памяти для начала?? (что я имею в виду, как он знает его значение? например, это (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):
[...] Если составной литерал находится вне тела функции, объект имеет статическую продолжительность хранения; в противном случае он имеет автоматическую продолжительность хранения, связанную с окружающим блоком.
Инициализация составными литералами выполняется следующим образом:
[...] Если блок вводится рекурсивно, каждый раз создается новый экземпляр объекта. Начальное значение объекта не определено. Если для объекта указана инициализация, она выполняется каждый раз, когда в ходе выполнения блока достигается объявление или составной литерал; в противном случае значение становится неопределенным при каждом достижении декларации.
Составные литералы создаются на лету и являются безымянными литералами массива / структуры. Они хранятся в сегменте данных памяти, как и другие переменные, но за исключением того, что вы не можете получить к ним доступ по имени.
Компиляция не знает значения автоматических переменных. Будет содержать все значения, которые уже были там. Иногда (например, в циклах) они могут содержать значение из прошлого раза, но это, к счастью, не намерение.
Компилятор помещает в стек автоматические переменные, смешанные с параметрами функции и адресами возврата. Если код явно не инициализирует их, автоматические переменные будут иметь значение любого битового паттерна, занятого в тех местах памяти ранее.