LINUX.ORG.RU

Использование const в аргументах функции в сях


0

1

Такой вопрос: в чем принципиальная разница между

void func(int arg)
и
void func(const int arg)
? Ведь во втором случае, получается, единственное чего нельзя делать - это использовать arg в качестве изменяемой локальной переменной. Но все равно ведь можно сделать так:
void func(const int arg) {
    int arg_local = arg;
    arg_local++;
    ...
}

Была мысль, что в первом случае нельзя сделать такой вызов:

func(5)
то есть подать на вход константу. Но gcc даже warning-ов не выдает. Так все же: имеет ли какой-то смысл использование const для стандартных типов в аргументах функции при передаче по значению (т. е. без использования указателей)?

★★★

Последнее исправление: Kiborg (всего исправлений: 1)
Ответ на: комментарий от LamerOk

Если нигде, то следует ли из этого, что sqlite - говно?

Из этого следует, что данный конкретный кусок кода — говно. Если такое повсеместно, то таки да, sqlite, выходит, говно.

А вот чем обходятся консты в реальной практике

И чем же они таким обходятся?

kemm
()
Ответ на: комментарий от LamerOk

Это - код, написанный задолго до С88/89, и даже после, для платформ, под которых стандартного компилятора просто могло не быть

А ещё Fortran77 есть, ага. И что?

Овер 9к «криворучек» жило и работало до выхода стандарта и появления совместимых с ним компиляторов. Даже код начала 90-ых частенько мог иметь K&R синтаксис

Мог. А ещё Fortran77 есть. И что? Это как-то мешает сейчас писать не через жопу (и не оправдывать написание черезжопного кода) везде, где это возможно?

kemm
()
Ответ на: комментарий от LamerOk

тем не менее const все же используется. Если его не использовали для zName - значит либо были причины, либо разработчики недосмотрели. я не знаком с кодом sqlite, не могу спорить.

Более того, в сишечке не принято даже данные по указателю защищать констом.

а теперь посмотри на свой же пример кода :)

vvviperrr ★★★★★
()
Ответ на: комментарий от kemm

А ещё Fortran77 есть, ага. И что?

Мог. А ещё Fortran77 есть. И что?

Что «И что?»? Норкоман? Какое отношение фортран имеет к предмету разговора?

Это как-то мешает сейчас писать ...

А при чём тут «сейчас»? Есть код. Есть компилятор. Код корректный и рабочий многие годы. С чего это вдруг компилятор при его виде должен голосить как бабка на базаре?

LamerOk ★★★★★
()
Ответ на: комментарий от LamerOk

и «наугад» было явно не наугад, т.к. в sqlite повсеместно const char*

wota ★★
()
Ответ на: комментарий от vvviperrr

тем не менее const все же используется.

Я уже согласился с этим вот тут: Использование const в аргументах функции в сях (комментарий)

Следи за дискуссией. ;)

Если его не использовали для zName - значит либо были причины

а теперь посмотри на свой же пример кода

В нём наглядно проиллюстрирована одна и возможных причин.

LamerOk ★★★★★
()
Ответ на: комментарий от LamerOk

Бггг. Много ли ты покажешь проектов, которые могут продемонстрировать что-то подобное: http://www.sqlite.org/testing.html ?

У тебя проблемы с логикой и русским языком? Сочувствую. Попробуй всё-таки разобраться с конструкцией «если А, то Б», а уже потом лезть в обсуждение программирования. И да, фигурная резьба по квоте — это старомодно, тут один хрен есть ссылка на оригинал сообщения.

А из примера не ясно?

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

kemm
()
Ответ на: комментарий от LamerOk

Что «И что?»? Норкоман? Какое отношение фортран имеет к предмету разговора?

Наркоман тут явно не я. При чём тут версии стандартов и компиляторы? Вот при том же и F77.

А при чём тут «сейчас»? Есть код. Есть компилятор. Код корректный и рабочий многие годы. С чего это вдруг компилятор при его виде должен голосить как бабка на базаре?

Обязан. Кому не нравятся — пусть подчистят код или скажут данные файлы компилировать с отключенным конкретно этим варнингом. С другой стороны, за 15+ лет я лично не видел ни одного случая, когда ругань gcc -Wall была совсем не по делу, а не означала хотя бы, что код хоть и корректен, но грязноват.

kemm
()
Ответ на: комментарий от LamerOk

И он после этого продолжает передёргивать про «sqlite — говно»? facepalm.tiff

kemm
()
Ответ на: комментарий от kemm

Попробуй всё-таки разобраться с конструкцией «если А, то Б», а уже потом лезть в обсуждение программирования.

Какой забавный вариант «спердобейся». Но в эту игру можно играть вдовём: напиши-ка сперва хоть один проект уровня sqlite, а потом обзывай его «говном». Берёшься?

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

Чего ты боишься предположить? Или ты боишся признаться в собственных когнитивных способностях в отношении простого сишного кода? Давай-ка мы их проверим. Вот тебе код:

SQLITE_PRIVATE void *sqlite3Fts3HashInsert(
  Fts3Hash *pH,        /* The hash table to insert into */
  const void *pKey,    /* The key */
  int nKey,            /* Number of bytes in the key */
  void *data           /* The data */
){
  int hraw;                 /* Raw hash value of the key */
  int h;                    /* the hash of the key modulo hash table size */
  Fts3HashElem *elem;       /* Used to loop thru the element list */
  Fts3HashElem *new_elem;   /* New element added to the pH */
  int (*xHash)(const void*,int);  /* The hash function */

  assert( pH!=0 );
  xHash = ftsHashFunction(pH->keyClass);
  assert( xHash!=0 );
  hraw = (*xHash)(pKey, nKey);
  assert( (pH->htsize & (pH->htsize-1))==0 );
  h = hraw & (pH->htsize-1);
  elem = fts3FindElementByHash(pH,pKey,nKey,h);
  if( elem ){
    void *old_data = elem->data;
    if( data==0 ){
      fts3RemoveElementByHash(pH,elem,h);
    }else{
      elem->data = data;
    }
    return old_data;
  }
  if( data==0 ) return 0;
  if( (pH->htsize==0 && fts3Rehash(pH,8))
   || (pH->count>=pH->htsize && fts3Rehash(pH, pH->htsize*2))
  ){
    pH->count = 0;
    return data;
  }
  assert( pH->htsize>0 );
  new_elem = (Fts3HashElem*)fts3HashMalloc( sizeof(Fts3HashElem) );
  if( new_elem==0 ) return data;
  if( pH->copyKey && pKey!=0 ){
    new_elem->pKey = fts3HashMalloc( nKey );
    if( new_elem->pKey==0 ){
      fts3HashFree(new_elem);
      return data;
    }
    memcpy((void*)new_elem->pKey, pKey, nKey);
  }else{
    new_elem->pKey = (void*)pKey;
  }
  new_elem->nKey = nKey;
  pH->count++;
  assert( pH->htsize>0 );
  assert( (pH->htsize & (pH->htsize-1))==0 );
  h = hraw & (pH->htsize-1);
  fts3HashInsertElement(pH, &pH->ht[h], new_elem);
  new_elem->data = data;
  return 0;
}

Твоя задача объяснить, соблюли ли разработчики sqlite твою религиозную заповедь в отношении void *data, и если нет, то почему. На всякий случай напоминаю тебе твою заповедь:

Если функция не меняет объект — она обязана иметь аргументом 'const object_type *', и никак иначе.

LamerOk ★★★★★
()
Ответ на: комментарий от kemm

Кому не нравятся — пусть подчистят код

Так ты из волшебной страны эльфов? Чего ж ты молчал-то, сказал бы сразу. Как там погода в Валиноре? ;)

LamerOk ★★★★★
()
Ответ на: комментарий от LamerOk

Какой забавный вариант «спердобейся». Но в эту игру можно играть вдовём: напиши-ка сперва хоть один проект уровня sqlite, а потом обзывай его «говном». Берёшься?

Т.е. ты всё-таки не смог разобраться с конструкцией «если А, то Б»? Сочувстсвую. Куда ссаные тряпки слать?

Твоя задача объяснить, соблюли ли разработчики sqlite твою религиозную заповедь в отношении void *data, и если нет, то почему

Ты не увиливай. Ответ на вопрос в студию.

kemm
()
Ответ на: комментарий от LamerOk

Так ты из волшебной страны эльфов? Чего ж ты молчал-то, сказал бы сразу. Как там погода в Валиноре? ;)

Грибочков перекушал? Ну бывает. Ты бы лучше завязывал с этим делом.

kemm
()
Ответ на: комментарий от LamerOk

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

kemm
()
Ответ на: комментарий от kemm

Ладно, дальнейший разговор с тобой по этому вопросу явно лишён смысла, так что в качестве прощального жеста отвечу тебе вот на этот вопрос:

При чём тут версии стандартов и компиляторы?

При том, что литеральный массив char стал константным только в первом стандарте Си 88/89 годов. Т.е. добрых десять - пятнадцать лет со времени создания языка строковые литералы были мутабельными массивами, и уйма кода, в т.ч. и юниксового, полагалась на это.

LamerOk ★★★★★
()
Ответ на: комментарий от LamerOk

Ладно, дальнейший разговор с тобой по этому вопросу явно лишён смысла

Так и запишем, что аргументов нет, а конструкция «если А, то Б» оказалась слишком неLamerской. 8)))

При том, что литеральный массив char стал константным только в первом стандарте Си 88/89 годов. Т.е. добрых десять - пятнадцать лет со времени создания языка строковые литералы были мутабельными массивами, и уйма кода, в т.ч. и юниксового, полагалась на это

facepalm.bmp

static void
foo(char *str)
{
    str[0] = '\0';
}

int
main()
{
    foo("string");
    return 0;
}

Полагались, говоришь? И предупреждений быть не должно, говоришь? Садись, кол.

kemm
()
Ответ на: комментарий от r2d2

Если в f(char *) передаётся строковый литерал или const char * переменная? Второе-то осталось, просто занакой-то чёрт литерал имеет теперь тип 'char *' (при этом попытка в него что-то записать всё так же ведёт к SIGSEGV'у, только теперь ещё и типы явно приводить не надо)

kemm
()
Ответ на: комментарий от kemm

Так и запишем, что аргументов нет

Если ты их не понял, это не значит, что их нет. ;) Даю тебе последний шанс.

Полагались, говоришь?

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

anon@nymous$ grep tfname -C3 -n V7/usr/src/cmd/ld.c
192-
193-int errlev;
194-int delarg  = 4;
195:char        tfname[] = "/tmp/ldaXXXXX";
196-
197-
198-/* output management */
--
711-setupout()
712-{
713-    tcreat(&toutb, 0);
714:    mktemp(tfname);
715-    tcreat(&doutb, 1);
716-    if (sflag==0 || xflag==0)
717-            tcreat(&soutb, 1);
--
741-{
742-    register int ufd;
743-    char *nam;
744:    nam = (tempflg ? tfname : ofilename);
745-    if ((ufd = creat(nam, 0666)) < 0)
746-            error(2, tempflg?"cannot create temp":"cannot create output");
747-    close(ufd);
748-    buf->fildes = open(nam, 2);
749-    if (tempflg)
750:            unlink(tfname);
751-    buf->nleft = sizeof(buf->iobuf)/sizeof(int);
752-    buf->xnext = buf->iobuf;
753-}

И предупреждений быть не должно, говоришь?

Говорю, что их _может_ не быть. Ибо:

6.4.5 String literals
...
5
In translation phase 7, a byte or code of value zero is appended to each multibyte
character sequence that results from a string literal or literals. The multibyte character
sequence is then used to initialize an array of static storage duration and length just
sufficient to contain the sequence. For character string literals, the array elements have
type char, and are initialized with the individual bytes of the multibyte character
sequence; for wide string literals, the array elements have type wchar_t,...

6
It is unspecified whether these arrays are distinct provided their elements have the
appropriate values. If the program attempts to modify such an array,the behavior is
undefined.

Садись, кол.

Нет, уж, спасибо, на свой кол садись сам.

LamerOk ★★★★★
()
Ответ на: комментарий от LamerOk

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

Ой, какой пафос-то. Только вот что ты сказать-то хотел, разжуй уж. А то ведь снова в лужу сел (третий раз подряд, кстати), не заметив этого.

Говорю, что их _может_ не быть. Ибо:

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

Единственный случай, когда предупреждения может не быть — когда реализация подразумевает возможность изменения памяти, где хранятся литералы. На абсолютном большинстве онтопичных платформ и компиляторов это не так (-fwriteable-strings похоронили ХЗ сколько времени назад). Садись, кол.

Нет, уж, спасибо, на свой кол садись сам.

Извини, я не виноват, что в тех лужах, куда ты так артистично плюхаешься, кто-то колов навтыкал остриём вверх. Ты уж там аккуратнее в следующий раз.

kemm
()
Ответ на: комментарий от kemm
const a;
'bar((int *)&a);'

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

  const int n = 18; 

  while (n > 0)
      (*((int *) &n))--;

backbone ★★★★★
()
Ответ на: комментарий от kemm

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

«Единственный разумный», естественно. Так-то компилятор может вообще не уметь ни одного предупреждения выдавать, только кому такое нужно.

kemm
()
Ответ на: комментарий от backbone

Но это же будет явным ссзб со стороны того, кто так сделал

Стрельба себе в ногу, давний, широко известный и очень популярный параолимпийский вид спорта. 8)) Просто, IMHO, надо таки иметь режим, в котором отключены все оптимизации — удивился, что в clang'е -O0 это не оно.

И без всяких функций такое приведение типов приведёт к ошибке.

Не, если столько скобочек нарисовать — этак можно срач «lisp vs everything» призвать ненароком. 8))

kemm
()
Ответ на: комментарий от kemm

Только вот что ты сказать-то хотел, разжуй уж.

Прости, раз ты не знаешь языка программирования, то объяснять тебе что-то на эту тему не имеет смысла.

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

Ба! Мы наблюдаем редкий проблеск разума.

На абсолютном большинстве ...

Следующим шагом тебе надо подтянуть логику предикатов. Особый упор сделать на кванторах.

LamerOk ★★★★★
()
Ответ на: комментарий от LamerOk

Прости, раз ты не знаешь языка программирования, то объяснять тебе что-то на эту тему не имеет смысла

Чья бы уж корова мычала. Ты три раза подряд сел в лужу со своим незнанием языка и, тем не менее, продолжаешь что-то говорить? Давай адрес, вышлю тебе K&R или ссаные тряпки (в зависимости от настроения, воспринимай как лотерею).

Пока будешь ждать посылку, подумай, чем отличаются конструкции:

const char *const_str = "literal";
char *str = "literal";
char non_const_str[] = "literal";

А то скоро весь пол зальёшь, плюхаясь в лужу.

Следующим шагом тебе надо подтянуть логику предикатов. Особый упор сделать на кванторах.

О, Великий Учитель, растолкуй там смысл выражения «если А, то Б»! А то куда уж нам до некоторых…

kemm
()
Ответ на: комментарий от LamerOk

+1, проверь. Я вчера специально программу накалякал - предупреждение есть.

Kiborg ★★★
() автор топика
Ответ на: комментарий от LamerOk

Не понял что мы должны были увидеть во второй простыне говногода. Вместо того, чтобы объявить z как const char *, в каждом вызове sqlite3Fts3NextToken зачем-то делается приведение типов. Но я в простыню не особо вчитывался, так как уж очень длинная.

Kiborg ★★★
() автор топика
Ответ на: комментарий от LamerOk

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

Kiborg ★★★
() автор топика
Ответ на: комментарий от Kiborg

Вместо того, чтобы объявить z как const char *, в каждом вызове sqlite3Fts3NextToken зачем-то делается приведение типов.

Спокойно, всё там нормально. Функция sqlite3Fts3NextToken получает на вход строку (константную) и выдаёт на выходе указатель на символ в этой же строке или NULL. Тут мы подаём ей на вход char * и можем с чистой совестью результат привести обратно к char * — всё в пределах одной функции, всё чисто. А так вариантов ровно 3:

  • собственно, оставить как есть (см. выше)
  • объявить z, zCopy и zEnd как const char * (фигово нарушением логики, z не константа даже в пределах функции, мы регулярно туда \0 записываем) и не менее постоянно кастить в строках типа z[n] = '\0'
  • сделать ещё функцию 'char *sqlite3Fts3NextToken2(char *)' — но это совсем уж извращение
kemm
()
Ответ на: комментарий от kemm

Функция sqlite3Fts3NextToken получает на вход строку (константную) ...

Молодец! Ведь можешь же, когда хочешь.

А так вариантов ровно 3:

Тоже правильно, если мы хотим оставить её первый аргумент указателем на иммутабельные данные. А теперь расскажи, зачем нам любой из этих трёх геммороев, и какую задачу в итоге мы решили с помощью const'а.

LamerOk ★★★★★
()
Ответ на: комментарий от kemm

Спасибо, чувак, но K&R у меня есть свой, а зассаные тобою тряпки, от которых ты, судя по теме, так жаждешь избавиться, как и засранные тобою штаны мне не нужны.

И да, публичный слив в форме отказа ответить на простейший вопрос засчитан. :3

LamerOk ★★★★★
()
Ответ на: комментарий от LamerOk

И да, публичный слив в форме отказа ответить на простейший вопрос засчитан. :3

Ну да, ты уже давно слился, только заметил? Инициализацию массива от присвоения указателя отличить не может — и всё туда же, про С бредить. Раз уж есть K&R — так ты его открой и почитай, а пока пошёл вон из профессии.

kemm
()
Ответ на: комментарий от kemm

Инициализацию массива от присвоения указателя отличить не может

Ой, дурак же ты, ой дурак... Мог бы по моему молчанию заподозрить неладное. ;)

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

LamerOk ★★★★★
()
Ответ на: комментарий от LamerOk

Слушай, не позорься уж дальше, а? Открой K&R и почитай всё же, чем инициализация массива отличается от присвоения указателя, и почему в функцию, принимающую 'char *' (и меняющую внутри аргумент) можно передавать проинициализированный литералом массив, но нельзя — сам литерал или указатель, которому он был присвоен. Я понимаю, что в твоём ПТУ это не объясняли, но весь мир им не ограничен.

И пошёл вон из профессии, говорящий никнейм. Лужи кончились, ты их уже все осушил.

kemm
()
Ответ на: комментарий от kemm
 z[n] = '\0';

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

Kiborg ★★★
() автор топика
Ответ на: комментарий от Eddy_Em

Мой gcc ругался, когда я const char * передавал в функцию, у которой в аргументах char * на этом месте указано.

Kiborg ★★★
() автор топика
Ответ на: комментарий от kemm

почему в функцию, принимающую 'char *' (и меняющую внутри аргумент) можно передавать проинициализированный литералом массив, но нельзя — сам литерал или указатель, которому он был присвоен.

За то, что нянчусь с тобой, мне нужно выдавать молоко бесплатно. Надеюсь у тебя достаточно крепенький шаблон, не треснет? ;)

usr/src/cmd/arcv.c:34:  tmp = mktemp("/tmp/arcXXXXX");
usr/src/cmd/diff.c:214:         *pa1 = tempfile = mktemp("/tmp/dXXXXX");
usr/src/cmd/ed.c:146:   tfname = mktemp("/tmp/eXXXXX");
usr/src/cmd/m4/m4.c:197:        tempname = mktemp("/tmp/m4aXXXXX");
usr/src/cmd/ptx.c:218:  sortfile = mktemp("/tmp/ptxsXXXXX");
usr/src/cmd/strip.c:19: tname = mktemp("/tmp/sXXXXX");
usr/src/cmd/troff/n1.c:331:     p = mktemp("/tmp/taXXXXX");

xDDD

LamerOk ★★★★★
()
Ответ на: комментарий от LamerOk

Малацца. Наконец-то, три раза показав, что С ты не знаешь, и громко плюхнувшись в лужу, сумел нарисовать что-то, что якобы подтверждает твои «мысли». Осталось понять, зачем ты это рисовал и что это якобы подтверждает? Что когда-то был такой язык, Fortran66 назывался, и поэтому все эти ваши while, for и if никому не нужны?

Ещё раз, медленно и два раза для выпускников (или выгнали?) ПТУ:

Отказ от предупреждения и смена типа строкового литерала в gcc — диверсия, потому что сейчас строковый литерал неизменяем. По факту. Что в 79ом году (или когда там было нипасино то говно мамонта, которым ты тут размахиваешь?) это могло быть не так — никого не волнует. Модификатор const у аргумента показывает (на уровне соглашений), что функция нигде внутри не похабит аргумент (и, как следствие, ей можно подсунуть литерал без большого БА-БАХ в случае char *), код, который не следует этим соглашениям и не помечает неизменяемые аргументы как const (возражения типа твоего плюха в лужу №2 не принимаются по очевидным любому сишному программисту причинам) — хреновый код, и среди известных проектов такое практически не встречается (да, и даже в sqlite, что бы криворуким косоглазикам не мерещилось).

Отказ от предупреждения и смена типа строкового литерала в gcc — диверсия, потому что сейчас строковый литерал неизменяем. По факту. Что в 79ом году (или когда там было нипасино то говно мамонта, которым ты тут размахиваешь?) это могло быть не так — никого не волнует. Модификатор const у аргумента показывает (на уровне соглашений), что функция нигде внутри не похабит аргумент (и, как следствие, ей можно подсунуть литерал без большого БА-БАХ в случае char *), код, который не следует этим соглашениям и не помечает неизменяемые аргументы как const (возражения типа твоего плюха в лужу №2 не принимаются по очевидным любому сишному программисту причинам) — хреновый код, и среди известных проектов такое практически не встречается (да, и даже в sqlite, что бы криворуким косоглазикам не мерещилось).

Так лучше доходит? Если нет — уточни про свой ПТУ, а то, видимо, тебя обманули, и это школа для крайне одарённых (жаль, что альтернативно) детей была.

kemm
()
Ответ на: комментарий от Legioner

это порождает слишком много лишних токенов и усложняет чтение программы

Ой, не скажи. грамотное использование const в разы улучшает читаемость кода. Например, если функция возвращает const TYPE*, это автоматически означает, что данное значение не нужно освобождать. const void * в *write/send функциях позволяет тебе использовать один и тот же буфер для отправки одинакового сообщения множеству клиентов.

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

red_eyed_peguin
()
Ответ на: комментарий от kemm

Эк тебя разбатхёртило-то...

Отказ от предупреждения и смена типа строкового литерала в gcc — диверсия, потому что сейчас строковый литерал неизменяем.

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

LamerOk ★★★★★
()
Ответ на: комментарий от LamerOk

Ясно, это всё ж таки было не ПТУ. Доучился хоть или даже оттуда за неуспеваемость выгнали?

kemm
()
Ответ на: комментарий от LamerOk

Много ли ты покажешь проектов, которые могут продемонстрировать что-то подобное: http://www.sqlite.org/testing.html ?

«Сначала мы написали тонну быдлокода, а потом написали две тонны тесткейсов» — подход имеет право на жизнь.

red_eyed_peguin
()
Ответ на: комментарий от red_eyed_peguin

Если ты считаешь, что const-спецификаторы в функциях плохо

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

LamerOk ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.