«Static const» против «#define» против «enum»


585

Какой из них лучше использовать среди приведенных ниже утверждений в C?

static const int var = 5;

или

#define var 5

или

enum { var = 5 };

35
Интересно, что это почти тот же вопрос, что и stackoverflow.com/questions/1637332/static-const-vs-define . Разница лишь в том, что этот вопрос касается C ++, а этот вопрос - C. Поскольку мой ответ был специфичным для C ++, я говорю, что они не идентичны, но другие могут не согласиться.
TED

53
Не идентично, определенно. Существует множество областей, где C ++ допускает синтаксис C по соображениям совместимости. В таких случаях такие вопросы, как «как лучше всего сделать X», будут иметь разные ответы в C ++. Например, инициализация объекта.
MSalters


Как это не основано на мнении? У каждого из них своя цель
Сэм Хаммами,

1
@RobertSsupportsMonicaCellio, Да. Спасибо за указание
Виджай

Ответы:


690

Это зависит от того, для чего вам нужно значение. Вы (и все остальные до сих пор) пропустили третий вариант:

  1. static const int var = 5;
  2. #define var 5
  3. enum { var = 5 };

Игнорируя вопросы о выборе имени, тогда:

  • Если вам нужно передать указатель, вы должны использовать (1).
  • Поскольку (2), по-видимому, вариант, вам не нужно указывать указатели.
  • Оба (1) и (3) имеют символ в таблице символов отладчика, что облегчает отладку. Скорее всего, у (2) не будет символа, и вам будет интересно, что это такое.
  • (1) нельзя использовать в качестве измерения для массивов в глобальной области видимости; оба (2) и (3) могут.
  • (1) нельзя использовать в качестве измерения для статических массивов в области действия функции; оба (2) и (3) могут.
  • Под C99 все это можно использовать для локальных массивов. Технически, использование (1) подразумевало бы использование VLA (массива переменной длины), хотя размерность, на которую ссылается 'var', конечно же, была бы зафиксирована в размере 5.
  • (1) нельзя использовать в таких местах, как операторы switch; оба (2) и (3) могут.
  • (1) нельзя использовать для инициализации статических переменных; оба (2) и (3) могут.
  • (2) может изменить код, который вы не хотели изменять, потому что он используется препроцессором; у обоих (1) и (3) не будет неожиданных побочных эффектов, подобных этому.
  • Вы можете определить, было ли (2) установлено в препроцессоре; ни (1), ни (3) не позволяют этого.

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

Если бы вы спрашивали о C ++, то каждый раз использовали бы option (1) - static const.


111
фантастический список! Недостатком enumявляется то, что они реализованы как int([C99] 6.7.2.2/3). A #defineпозволяет вам указать unsigned и long с Uи Lсуффиксами, а также constдать вам тип. enumможет вызвать проблемы с обычными преобразованиями типов.
Готье

37
(2) люди ВСЕГДА жалуются на безопасность типов. Я никогда не понимаю, почему бы просто не использовать «#define var ((int) 5)» и ура, вы получили безопасность типов с определением.
Инго Блэкман

6
@RedX: вы должны были бы находиться в очень специфической среде для беспокойства. Тем не менее, ни как и enumне #defineиспользует дополнительное пространство, как таковое. Значение будет отображаться в объектном коде как часть инструкций, а не как выделенное хранилище в сегменте данных, в куче или в стеке. У вас будет немного места для static const int, но компилятор может оптимизировать его, если вы не берете адрес.
Джонатан Леффлер

15
Еще один «голос» за enums (и static const): их нельзя изменить. a defineможет быть #undefine'd, где an enumи static constфиксированы к данному значению.
Даан Тиммер

15
@QED: нет, спасибо. Простая константа безопасна вне скобок. Или, покажите мне, как программа, которую можно было бы законно компилировать, изменилась бы, если бы в скобках не было 5. Если бы это был аргумент макроса в стиле функции, или если в выражении были какие-либо операторы, то вы бы правильно обвинить меня, если бы я не включил скобки. Но это не тот случай.
Джонатан Леффлер

282

Вообще говоря:

static const

Потому что он уважает область и является типобезопасным.

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

#ifdef VAR // Very bad name, not long enough, too general, etc..
  static int const var = VAR;
#else
  static int const var = 5; // default value
#endif

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

Если вам действительно НУЖНО идти с макросом (например, вы хотите __FILE__или __LINE__), то вам лучше ОЧЕНЬ назвать свой макрос ОЧЕНЬ осторожно: в соглашении об именах Boost рекомендует использовать все прописные буквы, начиная с имени проекта (здесь BOOST_ ), просматривая библиотеку, вы заметите, что за ней (обычно) следует имя конкретной области (библиотеки), а затем значимое имя.

Это обычно делает для длинных имен :)


2
Согласен - также с #define существует общая опасность искажения кода, поскольку препроцессор не знает синтаксиса.
NeilDurant

10
Лучше использовать #if, чем #ifdef, но в остальном я согласен. +1.
Тим Пост

58
Это стандартная евангелизация С ++. Ответ, приведенный ниже, НАМНОГО яснее в объяснении того, что варианты на самом деле означают. В частности: у меня только что была проблема со "статическим констатом". Кто-то использовал его, чтобы определить около 2000 «констант» в заголовочном файле. Затем этот заголовочный файл был включен примерно в 100 файлов ".c" и ".cpp". => 8 Мбайт для "consts" Отлично. Да, я знаю, что вы могли бы использовать компоновщик для удаления неконсервированных констант, но тогда это все равно оставляет вам «констант», на которые ссылаются. Не хватает места, что не так с этим ответом.
Инго Блэкман

2
@IngoBlackman: при хорошем компиляторе staticдолжны оставаться только те, чей адрес взят; и если адрес был взят, никто не мог использовать #defineили enum(без адреса) ... поэтому я действительно не вижу, какую альтернативу можно было бы использовать. Если вы можете покончить с «оценкой времени компиляции», вы можете искать extern constвместо этого.
Матье М.

15
@ Тим Заголовок сообщения: #ifможет быть предпочтительнее #ifdefдля логических флагов, но в данном случае это сделало бы невозможным определить , varкак 0из командной строки. Так что в этом случае, #ifdefимеет больше смысла, поскольку 0это законное значение для var.
Мартен

108

В C конкретно? В C правильный ответ: использовать #define(или, если это необходимо, enum)

Хотя полезно иметь свойства объема и типа constобъекта, в действительности constобъекты в C (в отличие от C ++) не являются истинными константами и поэтому обычно бесполезны в большинстве практических случаев.

Таким образом, в C выбор должен определяться тем, как вы планируете использовать свою константу. Например, вы не можете использовать const intобъект в качестве caseметки (в то время как макрос будет работать). Вы не можете использовать const intобъект в качестве ширины битового поля (пока макрос будет работать). В C89 / 90 вы не можете использовать constобъект для указания размера массива (в то время как макрос будет работать). Даже в C99 вы не можете использовать constобъект для указания размера массива, когда вам нужен массив без VLA .

Если это важно для вас, это определит ваш выбор. В большинстве случаев у вас не будет выбора, кроме как использовать #defineв C. И не забывайте другую альтернативу, которая производит истинные константы в C - enum.

В C ++ constобъекты являются истинными константами, поэтому в C ++ почти всегда лучше отдавать предпочтение constварианту ( staticхотя в C ++ нет необходимости явно указывать).


6
«Вы не можете использовать объект const int в качестве метки регистра (в то время как макрос будет работать)» ---> Что касается этого утверждения, я протестировал переменную const int в C в switch-case, он работает ....
Джон

8
@john: Ну, вам нужно предоставить код, который вы тестировали, и назвать конкретный компилятор. Использование const intобъектов в case-label запрещено во всех версиях языка Си. (Разумеется, ваш компилятор может поддерживать его как нестандартное расширение на языке C ++.)
AnT

11
«... и поэтому обычно бесполезны в большинстве практических случаев ». Я не согласен. Они очень полезны, если вам не нужно использовать имя в качестве константного выражения. Слово «константа» в C означает то, что может быть оценено во время компиляции; constозначает только для чтения. const int r = rand();совершенно законно.
Кит Томпсон

В c ++ лучше использовать, constexprчем в случае constс stlконтейнерами типа arrayor bitset.
Маюх Саркар

1
@ Джон, ты, должно быть, проверил в switch()утверждении, а не в caseодном. Я тоже попался на этом ☺
Привет-ангел

32

Разница между static constи #defineзаключается в том, что первый использует память, а второй не использует память для хранения. Во-вторых, вы не можете передать адрес a, #defineтогда как вы можете передать адрес a static const. На самом деле, в зависимости от того, в каких обстоятельствах мы находимся, нам нужно выбрать одно из этих двух. Оба в своих лучших проявлениях при разных обстоятельствах. Пожалуйста, не думайте, что одно лучше другого ... :-)

Если бы это было так, Деннис Ричи оставил бы лучшего в покое ... хахаха ... :-)


6
+1 за упоминание памяти, некоторые встроенные системы все еще не имеют так много, хотя я бы, вероятно, начал с использования статических констант и изменил бы на #defines только при необходимости.
fluffyben

3
Я только что проверил это. Действительно, const int использует дополнительную память по сравнению с #define или enum. Поскольку мы программируем встроенные системы, мы не можем позволить себе дополнительное использование памяти. Итак, мы вернемся к использованию #define или enum.
Давиде Андреа

2
Практически говоря, это не правда (больше), что constдействительно использует память. GCC (протестированный с 4.5.3 и несколькими более новыми версиями) легко оптимизирует const intпрямой код в вашем коде при использовании -O3. Таким образом, если вы занимаетесь разработкой встроенных систем с небольшим объемом ОЗУ (например, AVR), вы можете безопасно использовать C-константы, если используете GCC или другой совместимый компилятор. Я не проверял это, но ожидаю, что Clang сделает то же самое, кстати.
Рафаэль

19

В Си #defineгораздо популярнее. Вы можете использовать эти значения для объявления размеров массива, например:

#define MAXLEN 5

void foo(void) {
   int bar[MAXLEN];
}

static constНасколько мне известно, ANSI C не позволяет вам использовать s в этом контексте. В C ++ вы должны избегать макросов в этих случаях. Ты можешь написать

const int maxlen = 5;

void foo() {
   int bar[maxlen];
}

и даже опустить, staticпотому что внутренняя связь подразумевается constуже [только в C ++].


1
Что вы имеете в виду под "внутренней связью"? Я могу иметь const int MY_CONSTANT = 5;в одном файле и доступ к нему extern const int MY_CONSTANT;в другом. Я не смог найти в стандарте никакой информации (по крайней мере, C99) об constизменении поведения по умолчанию: «6.2.2: 5 Если объявление идентификатора для объекта имеет область видимости файла и не имеет спецификатора класса хранения, его связь является внешней».
Готье

@ Gauthier: Извините, об этом. Я должен был сказать «подразумевается const уже в языке C ++». Это специфично для C ++.
Sellibitze

@sellibitze приятно видеть несколько аргументов по пути вместо тонны МНЕНИЯ. Если бы был бонус за истинные аргументы, вы его получили!
Пол

1
Начиная с C99, ваш второй фрагмент допустим. barявляется VLA (массив переменной длины); компилятор может генерировать код, как если бы его длина была постоянной.
Кит Томпсон

14

Еще одним недостатком constC является то, что вы не можете использовать значение при инициализации другого const.

static int const NUMBER_OF_FINGERS_PER_HAND = 5;
static int const NUMBER_OF_HANDS = 2;

// initializer element is not constant, this does not work.
static int const NUMBER_OF_FINGERS = NUMBER_OF_FINGERS_PER_HAND 
                                     * NUMBER_OF_HANDS;

Даже это не работает с const, так как компилятор не видит его как константу:

static uint8_t const ARRAY_SIZE = 16;
static int8_t const lookup_table[ARRAY_SIZE] = {
    1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; // ARRAY_SIZE not a constant!

Я был бы рад использовать напечатанный constв этих случаях, иначе ...


5
Немного опоздал к игре, но этот вопрос возник в другом вопросе. Выследить, почему ваши static uint8_t const ARRAY_SIZE = 16;внезапно перестали компилироваться, может быть немного сложнее, особенно когда они #define ARRAY_SIZE 256зарыты в десять слоев глубоко в запутанной паутине заголовков. Это имя всех заглавных букв ARRAY_SIZEнапрашивается на неприятности. Зарезервируйте ALL_CAPS для макросов и никогда не определяйте макрос, который не находится в форме ALL_CAPS.
Дэвид Хаммен,

@ Дэвид: здравый совет, которому я буду следовать.
Готье

1
Спустя 4 года вы сэкономили мне много времени на то, чтобы понять, почему я не могу «гнездиться» const. Это может быть проголосовано больше!
Plouff

11

Если вы можете сойти с рук, static constимеет много преимуществ. Он подчиняется обычным принципам области видимости, отображается в отладчике и в целом подчиняется правилам, которым подчиняются переменные.

Тем не менее, по крайней мере, в оригинальном стандарте C он не является константой. Если вы используете #define var 5, вы можете написать int foo[var];как объявление, но вы не можете сделать это (кроме как как расширение компилятора) с static const int var = 5;. Это не так в C ++, где static constверсия может использоваться везде , где может #defineверсия, и я считаю, что это также случай с C99.

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


6
К сожалению, это не так с C99. constв С99 это еще не настоящая константа. Вы можете объявить размер массива с помощью constC99, но только потому, что C99 поддерживает массивы переменной длины. По этой причине он будет работать только там, где разрешены VLA. Например, даже в C99 вы все еще не можете использовать constдля объявления размера массива-члена в struct.
AnT

Хотя это верно, что C99 не позволит вам сделать это, GCC (протестированный с 4.5.3) прекрасно позволит вам инициализировать массивы с const intразмером, как если бы это был константа C ++ или макрос. Если вы хотите зависеть от этого отклонения GCC от стандарта, это, конечно, ваш выбор, я бы лично согласился с этим, если вы не можете на самом деле предвидеть использование другого компилятора, кроме GCC или Clang, последний обладает той же функцией здесь (протестировано с Clang). 3.7).
Рафаэль

7

ВСЕГДА предпочтительно использовать const вместо #define. Это потому, что const обрабатывается компилятором, а #define - препроцессором. Это похоже на то, что #define сам по себе не является частью кода (грубо говоря).

Пример:

#define PI 3.1416

Символическое имя PI никогда не будет видно компиляторам; он может быть удален препроцессором до того, как исходный код попадет в компилятор. В результате имя PI может не войти в таблицу символов. Это может сбивать с толку, если во время компиляции вы получаете ошибку, связанную с использованием константы, поскольку сообщение об ошибке может относиться к 3.1416, а не к PI. Если бы PI был определен в заголовочном файле, который вы не написали, вы бы не знали, откуда взялся этот 3.1416.

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

Решение:

const double PI = 3.1416; //or static const...

6

#define var 5принесет вам неприятности, если у вас есть такие вещи, как mystruct.var.

Например,

struct mystruct {
    int var;
};

#define var 5

int main() {
    struct mystruct foo;
    foo.var = 1;
    return 0;
}

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


6

Я написал программу быстрого тестирования, чтобы продемонстрировать одно отличие:

#include <stdio.h>

enum {ENUM_DEFINED=16};
enum {ENUM_DEFINED=32};

#define DEFINED_DEFINED 16
#define DEFINED_DEFINED 32

int main(int argc, char *argv[]) {

   printf("%d, %d\n", DEFINED_DEFINED, ENUM_DEFINED);

   return(0);
}

Это компилируется с этими ошибками и предупреждениями:

main.c:6:7: error: redefinition of enumerator 'ENUM_DEFINED'
enum {ENUM_DEFINED=32};
      ^
main.c:5:7: note: previous definition is here
enum {ENUM_DEFINED=16};
      ^
main.c:9:9: warning: 'DEFINED_DEFINED' macro redefined [-Wmacro-redefined]
#define DEFINED_DEFINED 32
        ^
main.c:8:9: note: previous definition is here
#define DEFINED_DEFINED 16
        ^

Обратите внимание, что enum выдает ошибку, когда define дает предупреждение.


4

Определение

const int const_value = 5;

не всегда определяет постоянное значение. Некоторые компиляторы (например, tcc 0.9.26 ) просто выделяют память, идентифицированную с именем "const_value". Используя идентификатор "const_value", вы не можете изменить эту память. Но вы все равно можете изменить память, используя другой идентификатор:

const int const_value = 5;
int *mutable_value = (int*) &const_value;
*mutable_value = 3;
printf("%i", const_value); // The output may be 5 or 3, depending on the compiler.

Это означает определение

#define CONST_VALUE 5

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


8
Изменение постоянного значения с помощью указателя - неопределенное поведение. Если вы готовы пойти туда, #defineтакже можете изменить, отредактировав машинный код.
Угорен

Ты отчасти прав. Я проверил код с Visual Studio 2012, и он печатает 5. Но нельзя изменить, #defineпотому что это макрос препроцессора. Это не существует в двоичной программе. Если кто-то хотел изменить все места, где CONST_VALUEон использовался, он должен был сделать это одно за другим.
user2229691

3
@ugoren: Предположим, вы пишете #define CONST 5, if (CONST == 5) { do_this(); } else { do_that(); }а компилятор удаляет elseветку. Как вы предлагаете редактировать машинный код, чтобы изменить CONSTна 6?
Кит Томпсон

@KeithThompson, я никогда не говорил, что это можно сделать легко и надежно. Просто это #defineне пуленепробиваемый.
Угорен

3
@ugoren: Моя точка зрения заключается в том, что «редактирование машинного кода» не является разумным способом дублировать эффект изменения значения a #define. Единственный реальный способ сделать это - отредактировать исходный код и перекомпилировать.
Кит Томпсон

4

Хотя вопрос был о целых числах, стоит отметить, что #define и перечисления бесполезны, если вам нужна постоянная структура или строка. Они оба обычно передаются функциям как указатели. (Со строками это требуется; со структурами это намного более эффективно.)

Что касается целых чисел, если вы находитесь во встроенной среде с очень ограниченным объемом памяти, вам может потребоваться беспокоиться о том, где хранится константа и как компилируется доступ к ней. Компилятор может добавить две константы во время выполнения, но добавить две #defines во время компиляции. Константа #define может быть преобразована в одну или несколько MOV [немедленных] инструкций, что означает, что константа эффективно сохраняется в памяти программы. Константа const будет храниться в разделе .const в памяти данных. В системах с Гарвардской архитектурой могут быть различия в производительности и использовании памяти, хотя они, вероятно, будут небольшими. Они могут иметь значение для оптимизации внутренних циклов.


3

Не думаю, что есть ответ на вопрос «что всегда лучше», но, как сказал Матье

static const

Тип безопасен. Моя самая большая неприятность с тем #define, что при отладке в Visual Studio вы не можете наблюдать за переменной. Это дает ошибку, что символ не может быть найден.


1
«Вы не можете смотреть переменную» Верно, это не переменная. Это не меняется, зачем тебе это смотреть? Вы можете найти везде, где он используется, просто ища ярлык. Зачем вам нужно (или даже хотеть) смотреть #define?
Маршал Юбэнкс

3

Между прочим, альтернативой #define, которая обеспечивает правильную область видимости, но ведет себя как «настоящая» константа, является «enum». Например:

enum {number_ten = 10;}

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

Однако следует сделать одно важное предостережение: в C ++ перечисляемые типы имеют ограниченную совместимость с целыми числами. Например, по умолчанию нельзя выполнять арифметику с ними. Я считаю это странным поведением по умолчанию для перечислений; хотя было бы неплохо иметь тип "строгого перечисления", учитывая желание иметь C ++, в целом совместимый с C, я бы подумал, что поведение по умолчанию для типа "enum" должно быть взаимозаменяемым с целыми числами.


1
В C константы перечисления всегда имеют тип int, поэтому «enum hack» не может использоваться с другими целочисленными типами. (Перечисление типа совместит с некоторым определяемой реализацией целого типа, не обязательно int, но в этом случае типа является анонимным , так что не имеет значения.)
Кит Томпсон

@KeithThompson: так как я написал выше, я прочитал, что MISRA-C будет кричать, если компилятор назначит тип, отличный от intпеременной с типом перечисления (какие компиляторы могут это делать), и каждый пытается присвоить такую ​​переменную член собственного перечисления. Хотелось бы, чтобы комитеты по стандартам добавили переносимые способы объявления целочисленных типов с заданной семантикой. ЛЮБАЯ платформа, независимо от charразмера, должна иметь возможность, например, объявлять тип, который обернет мод 65536, даже если компилятор должен добавить множество AND R0,#0xFFFFили эквивалентные инструкции.
суперкат

Вы можете использовать uint16_t, хотя, конечно, это не тип перечисления. Было бы неплохо позволить пользователю указать целочисленный тип, используемый для представления данного типа перечисления, но вы можете добиться почти такого же эффекта с помощью typedeffor uint16_tи ряда #defines для отдельных значений.
Кит Томпсон

1
@KeithThompson: Я понимаю, что по историческим причинам мы застряли с тем фактом, что некоторые платформы будут оценивать 2U < -1Lкак истинные, а другие как ложные, и теперь мы застряли с тем фактом, что некоторые платформы будут осуществлять сравнение между uint32_tи int32_tкак подписанные а некоторые как неподписанные, но это не означает, что Комитет не может определить совместимого вверх преемника C, который включает типы, семантика которых будет согласована на всех компиляторах.
суперкат

1

Простая разница:

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

Как и следовало ожидать, определить быстрее, чем статическое const.

Например, имея:

#define mymax 100

ты не можешь сделать printf("address of constant is %p",&mymax); .

Но имея

const int mymax_var=100

ты можешь сделать printf("address of constant is %p",&mymax_var); .

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

Однако для статического const у нас есть переменная, которая где-то размещена. Для gcc статические константы размещаются в текстовом сегменте программы.

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


1
Ваш ответ очень неправильный. Речь идет о C, ваш ответ относится к C ++, который имеет совсем другую семантику для constклассификатора. C не имеет символьных констант, кроме перечислимых . А const intявляется переменной. Вы также путаете язык и конкретные реализации. Там нет требования, где разместить объект. И это даже не верно для gcc: обычно это помещает constквалифицированные переменные в .rodataраздел. Но это зависит от целевой платформы. А ты имеешь ввиду адрес оператора &.
слишком честно для этого сайта

0

Мы рассмотрели полученный ассемблерный код на MBF16X ... Оба варианта приводят к одному и тому же коду для арифметических операций (например, ADD Immediate).

Так const intчто предпочтительнее для проверки типов, пока #defineэто старый стиль. Может быть, это зависит от компилятора. Так что проверьте ваш созданный код ассемблера


-1

Я не уверен, прав ли я, но, по моему мнению, вызов #defineзначения d намного быстрее, чем вызов любой другой обычно объявленной переменной (или константного значения). Это потому, что когда программа работает и ей нужно использовать некоторую обычно объявленную переменную, ей нужно перейти в точное место в памяти, чтобы получить эту переменную.

Напротив, когда используется #defineзначение d, программе не нужно переходить к какой-либо выделенной памяти, она просто принимает значение. Если #define myValue 7и программа вызывает myValue, она ведет себя точно так же, как и при простом вызове 7.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.