постоянный массив против статического постоянного массива в функции

#c #arrays #static #constants

#c #массивы #статический #константы

Вопрос:

Например, есть функция, которая что-то делает. Как я должен объявить и определить массив внутри функции, которую я хотел бы выделить / инициализировать только один раз?

 void someclass::somefunction(/*parameters here*/)
{
  static const my_array[4] = {1,2,3,4}; // #1
  /*or just*/
  const my_array[4] = {1,2,3,4}; // #2
}
  

Насколько я знаю, в случае # 1 «my_array» будет выделен в сегменте данных и инициализирован один раз при первом вызове «somefunction». Но мой коллега предположил, что случай № 2 работает таким же образом, и нет необходимости писать «статическое» ключевое слово.

Итак, я хотел бы спросить, говорит ли стандарт что-то о случаях # 1 и # 2, и если да, то что именно? Как я должен определить такой тип массивов, чтобы быть уверенным, что он будет выделен / инициализирован только один раз?

Спасибо.

Комментарии:

1. Когда я вижу static const , я думаю: «это действительно константа, для всей программы, она просто ограничена там, где это необходимо, чтобы она не протекала», но когда я вижу const без static , я думаю: «это константа для времени жизни функции». Они сообщают о разных намерениях (конкретно, static const сообщают о том же намерении, которое я бы перепроектировал из вашей логики кода в этом случае, что укрепило бы мое доверие к вашему коду, надежно связывающему вещи, делая чтение вашего кода более плавным и менее напряженным для меня в будущем. const сделал бы обратное.)

Ответ №1:

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

Ваш пример довольно тривиален, поскольку в массиве используются обычные старые данные (POD). Стандарт гласит, что опция 1 будет инициализироваться при каждом запуске somefunction , но опция 2 будет инициализироваться при первом запуске somefunction . Однако реализации могут отклоняться от этого, если результат неотличим от результата, указанного в стандарте, так называемом правиле as-if.

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

Если бы у вас был объект, требующий создания экземпляра во время выполнения, тогда все было бы по-другому. Рассмотрим поведение следующей программы:

 class MyObject
{
public:
  MyObject() {}
};

void f()
{
  const MyObject arr1[1] = { MyObject() };
  static const MyObject arr2[1] = { MyObject() };
}

int main(int argc, char* argv[])
{
  f();
  f();
  return 0;
}
  

Конструктор для MyObject выполняется 3 раза.

Комментарии:

1. Правильно ли я понимаю, что стандарт ничего не говорит о выделении / инициализации таких типов массивов?

2. Я расширил свой ответ, чтобы, надеюсь, ответить на этот вопрос.

3. Ссылка на стандарт C (и версию) была бы полезной.

Ответ №2:

Мой компилятор ( gcc 4.4.3 ) генерирует идентичный код в этих двух случаях.

Исходный код:

 void f()
{
  static const my_array[4] = {1,2,3,4}; // #1
}

void g()
{
  const my_array[4] = {1,2,3,4}; // #2
}
  

Результирующая сборка:

 f:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        movq    %rsp, %rbp
        .cfi_offset 6, -16
        .cfi_def_cfa_register 6
        leave
        ret
        .cfi_endproc

g:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        movq    %rsp, %rbp
        .cfi_offset 6, -16
        .cfi_def_cfa_register 6
        leave
        ret
        .cfi_endproc

        .section        .rodata

        .align 16
        .type   my_array.1594, @object
        .size   my_array.1594, 16
my_array.1594:
        .long   1
        .long   2
        .long   3
        .long   4

        .align 16
        .type   my_array.1591, @object
        .size   my_array.1591, 16
my_array.1591:
        .long   1
        .long   2
        .long   3
        .long   4
  

Я не знаю, ведут ли себя другие компиляторы таким же образом.

Ответ №3:

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

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

На самом деле единственный способ узнать наверняка для вашего конкретного компилятора — посмотреть на дизассемблирование.

Комментарии:

1. Для простой инициализации POD-данных нет необходимости в блокировках мьютекса во время инициализации. Конечно, вопрос был бы более интересным, если бы были инициализированы некоторые объекты.

Ответ №4:

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

 static struct {
    int a;
    char c;
} const someConstantMap[] = { { 1, 'a' }, { 2, 'b'}, { 0 } },
                    *mapi = someConstantMap;
  

в то время как исключение static даст вам массив const и итератор, как и следовало ожидать.