LINUX.ORG.RU

В чём смысл делать так_s постфиксить_t ?

 , , ,


0

2
typedef struct name_s
{
   ....
}name_t

Сабж по постфиксам.

Я делаю всегда так

typedef struct name_t 
{
   ....
}name_t

И стараюсь не не дать так

typedef struct
{
   ....
}name_t

Ибо pahole и иже с ним не могут в анонимные. Но в чём практический смысл задавать и _s и _t одновременно просто постфиксы вносят ясность и смягчают уровень былого отвращения к typedef нивелируя тот упрёк что с typedef теряется ясность. Но вернёмся к постфиксам. Ну или префиксам для типов аля t_uint8 t_int64 вместо uint8_t int64_t может тут есть извращенцы я не знаю :D

ТайпдеТупедефаете вы как?

★★★★★

Последнее исправление: LINUX-ORG-RU (всего исправлений: 3)

Ответ на: комментарий от byko3y

И к чему ты это скопипастил?

declared in separate translation units

У нас один translation unit.

If one is declared with a tag, theother shall be declared with the same tag.

S и T это не the same tag.

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

Нет, конечно. Моя религия недоумевает что это делать необходимо

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

Чтобы она не испарилась из-под твоего носа.

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

Ещё раз. Не рассматриваются подвыражения, когда мы рассматриваем чтение/запись через всё выражение.

(a + b)*2. Я не должен рассматривать a + b, потому что в конце все равно происходит умножение? Если у тебя подвыражение некорректно, то некорректным становится и всё выражение.

Т.е. когда в EL = ER мы интересуемся, например, чтением через ER, то с т.з. strict aliasing rule важен только тип ER и тип объекта, который оно обозначает. Даже если ER имеет вид (*(E1)).name.

«*E1» - это lvalue и оно обозначает доступ к объекту. Именно доступ к объекту и покрывается 6.5 параграф 7.

Поэтому и надо переписать, чтобы правила были явно упомянуты и всякие недоучки не думали, что при доступе через выражения E вида E1.name, strict aliasing rule рассматривает не только тип E, но и тип E1.

Да, при доступе через E1.name strict aliasing покрывает E1.name, E1, и содержащиеся в E1 подвыражения, производящие доступ к памяти.

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

If one is declared with a tag, theother shall be declared with the same tag.

S и T это не the same tag.

Хм-м-м, и то правда. Получается, что GCC прав. То есть, по strict aliasing в общем случае приведение типов через указатели вообще запрещено, кроме некоторых исключений. Прикольно. Тогда код

struct S { int i; } s;
struct T { int i; } t;
int* pi = &((T*)&s)->i;

является UB уже в таком виде, поскольку pi не обязано по стандарту иметь какое-то конкретное значение здесь.

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

(a + b)*2. Я не должен рассматривать a + b, потому что в конце все равно происходит умножение? Если у тебя подвыражение некорректно, то некорректным становится и всё выражение.

🤦‍♂️

«*E1» - это lvalue и оно обозначает доступ к объекту.

Само по себе применение * это ещё не доступ.

Именно доступ к объекту и покрывается 6.5 параграф 7.

Именно. А не-доступ — не покрывается.

Да, при доступе через E1.name strict aliasing покрывает E1.name, E1, и содержащиеся в E1 подвыражения, производящие доступ к памяти.

Да, вот только при доступе через E1.name, не нужно смотреть на тип E1 или какого-то произвольного подвыражения в E1, а только на тип E1.name. Потому что доступ — он через E1.name, а не E1.

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

То есть, по strict aliasing в общем случае приведение типов через указатели вообще запрещено

strict aliasing это вообще не про приведение типов. А про доступ.

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

(a + b)*2. Я не должен рассматривать a + b, потому что в конце все равно происходит умножение?

Если есть код вида

int a = 0;
float b = 0;
(a + b)*2;

То при чтении через lvalue a не нужно смотреть на тип lvalue b и говорить, что тут нарушение strict aliasing rule, т.к. объект типа int читается через lvalue типа `float.

Точно так же, при чтении члена структуры через выражение вида E1.name тебя не колышет тип подвыражения E1, а только непосредственно тип всего выражения E1.name.

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

Само по себе применение * это ещё не доступ
...
Да, вот только при доступе через E1.name, не нужно смотреть на тип E1 или какого-то произвольного подвыражения в E1, а только на тип E1.name. Потому что доступ — он через E1.name, а не E1

С такими раскладами можно дойти до того, что «E1.name» - это тоже не доступ, ведь само по себе это выражение ничего не читает и не изменяет. Нужно понимать, что логика языка не имеет отношения к тому, как он компилируется в машинный код. Например, потому что компилятор может вообще выкинуть эту инструкцию из программы, но описанная логика исходного кода при этом никуда не денется. В каком-то смысле потому в стандарте используется совершенно иной язык, и, например, «E1.name» описано как lvalue со значением именованного члена «name» объекта, на который указывает выражение E1.
6.5 параграф 7 ограничивается лишь высказываением «stored value accessed», что есть весьма размытая формулировка, с этим я спорить не буду.

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

strict aliasing это вообще не про приведение типов. А про доступ

Ну да, ты понял, что я хотел сказать. Я просто не знаю, как назвать операцию интерпретации одних и тех же данных как разные типы данных.

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

То при чтении через lvalue a не нужно смотреть на тип lvalue b и говорить, что тут нарушение strict aliasing rule, т.к. объект типа int читается через lvalue типа `float

Это type coercion, потому из другой оперы. Если a или b не являются корректными выражениями, вроде "(()+2)*2", то всё выражение становится некорректным.

byko3y ★★★★
()

_t для POSIX, а не обычного хлама, который ты пишешь.

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

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

Само по себе — да. Но если появляется в контексте, требущем значения, то происходит доступ этим lvalue. В стандарте C не очень удачно написано «by an lvalue». В стандарте плюсов полутши — «through a glvalue».

Кстати, заметь, стандарт говорит «an lvalue»/«a glvalue», т.е. в единственном числе. Т.е. доступ происходит через одно lvalue и тип одного lvalue важен. Т.е., ещё раз повторю, при доступе через E1.name, тип E1 нас не колышет. Не через него происходит доступ.

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

Доступ через volatile lvalue компилятор не может выкидывать.

struct S { volatile int i; } s;
volatile int* p = &s.i;

Тут не будет доступа (чтения) через s.i, т.к. такой доступ и не должен происходить в соответствие с правилами вычисления в абстрактной машине C или C++, т.к. чтение происходит тогда, когда нужно значение. А значение тут не нужно.

Теперь можно вернуться к

struct S { int i; } s;
struct T { int i; } t;
int* pi = &((T*)&s)->i;

В &((T*)&s)->i нет доступа (чтения или записи). Не потому что компилятор выкинул. Его нет в соответствие с правилами вычисления в абстрактной машине. Поэтому strict aliasing rule притянуть сюда нельзя.

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

Я не о том хотел сказать, ну да фиг с тобой.

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

Кстати, заметь, стандарт говорит «an lvalue»/«a glvalue», т.е. в единственном числе. Т.е. доступ происходит через одно lvalue и тип одного lvalue важен. Т.е., ещё раз повторю, при доступе через E1.name, тип E1 нас не колышет. Не через него происходит доступ

И не через «E1.name» происходит доступ. Дальше что? Пишем что захочется?

Доступ через volatile lvalue компилятор не может выкидывать.
struct S { volatile int i; } s;
volatile int* p = &s.i;
Тут не будет доступа (чтения) через s.i, т.к. такой доступ и не должен происходить в соответствие с правилами вычисления в абстрактной машине C или C++, т.к. чтение происходит тогда, когда нужно значение. А значение тут не нужно.

Так что ж получается, синтаксическая корректность программы определяется опциями компилятора-оптимизатора?

В &((T*)&s)->i нет доступа (чтения или записи). Не потому что компилятор выкинул. Его нет в соответствие с правилами вычисления в абстрактной машине. Поэтому strict aliasing rule притянуть сюда нельзя

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

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

И не через «E1.name» происходит доступ. Дальше что? Пишем что захочется?

Не разобрал что ты тут пытаешься сказать.

Так что ж получается, синтаксическая корректность программы определяется опциями компилятора-оптимизатора?

Откуда ты сделал такой бредовый вывод?

Strict aliasing сюда притянуть нельзя.

Да. И сюда тоже.

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

А в какой момент «начинается» UB?

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

Так что ж получается, синтаксическая корректность программы определяется опциями компилятора-оптимизатора?

Откуда ты сделал такой бредовый вывод?

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

И не через «E1.name» происходит доступ. Дальше что? Пишем что захочется?

Не разобрал что ты тут пытаешься сказать.

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

Strict aliasing сюда притянуть нельзя.

Да. И сюда тоже

Там есть чтение-запись памяти по измененному типу указателя, у тебя - нет. Потому там strict aliasing работает и ломает работу программы, а у тебя - нет.

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

А в какой момент «начинается» UB?

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

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

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

С чего ты взял про фактический? Я говорил про доступ в соответствие с правилами абстрактной машины C.

Потому там strict aliasing работает и ломает работу программы

Там UB не из-за нарушения правил strict aliasing. С ними всё ОК. UB там точно так же, как у меня — из-за некорректного использования скастованного указателя.

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

Я думал ты понял, что нет никакого доступа в &((T*)&s)->i. Ни «фактически», ни при вычислении в абстрактной машине C.

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

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

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

volatile значит, что значение может измениться извне, вот и всё.

Познания на уровне «strict aliasing это про то, какие указатели в какие кастить можно».

anonymous
()

Я делаю третий вариант, причем без этих ваших _t. Чем я рискую, что делаю не так?

I-Love-Microsoft ★★★★★
()
Ответ на: комментарий от anonymous
typedef struct  FlTypeField__ {

...

// --------------------------------------------------------
// --- Этот оператор позволяет FlTypeField__ присвоить значение типа ULONG
//
FlTypeField__ & operator=(
 USHORT  Flags
) {

union FlTypeFieldUnion {                                   // Для манипуляции с битами FlTypeField_

 FlTypeField__  FlTypeField01;                             // 
 USHORT         FlTypeField;                               // 

};

 FlTypeFieldUnion  FlTypeFieldUnion01;                     // 

 FlTypeFieldUnion01.FlTypeField = Flags;

 *this = FlTypeFieldUnion01.FlTypeField01;

 return  *this;

}                                                          // FlTypeField__ & operator=(

} FlTypeField_;

Владимир
anonymous
()
Ответ на: комментарий от anonymous

Я говорил про доступ в соответствие с правилами абстрактной машины C

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

Там UB не из-за нарушения правил strict aliasing. С ними всё ОК. UB там точно так же, как у меня — из-за некорректного использования скастованного указателя

Все старые компиляторы Си и новые с отключенными strict aliasing корректно выполняют код. Давай думать, каким образом при отключении strict aliasing не он самый оказывается виновником проблемы?

Я думал ты понял, что нет никакого доступа в &((T*)&s)->i

Я понял, хотя я и не разжевал свою позицию прямо. Тем не менее, смещение T.i относительно S.i стандартом не гарантируется.

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

ISO/IEC 9899:201x

6.2.7 Compatible type and composite type

Two types have compatible type if their types are the same. Additional rules for determining whether two types are compatible are described in 6.7.2 for type specifiers, in 6.7.3 for type qualifiers, and in 6.7.6 for declarators. 55)

  1. Two types need not be identical to be compatible.

6.5.2.3 Structure and union members

6 One special guarantee is made in order to simplify the use of unions: if a union contains several structures that share a common initial sequence (see below), and if the union object currently contains one of these structures, it is permitted to inspect the common initial part of any of them anywhere that a declaration of the completed type of the union is visible. Tw o structures share a common initial sequence if corresponding members have compatible type

shkolnick-kun ★★★★★
()
Последнее исправление: shkolnick-kun (всего исправлений: 3)
Ответ на: комментарий от shkolnick-kun

Покажи мне, где в стандарте говорится про стрикт алиассинг?

Нигде. Strict aliasing - это интерпертация стандарта. Сам стандарт, кажется, вообще не предназначен для чтения людьми.

byko3y ★★★★
()
Последнее исправление: byko3y (всего исправлений: 1)
Ответ на: комментарий от shkolnick-kun

http://www.iso-9899.info/n1570.html

Раздел 6.5, пункт 7.

7   An object shall have its stored value accessed only by an lvalue expression that has one of
    the following types:88)
    -- a type compatible with the effective type of the object,
    -- a qualified version of a type compatible with the effective type of the object,
    -- a type that is the signed or unsigned type corresponding to the effective type of the
       object,
    -- a type that is the signed or unsigned type corresponding to a qualified version of the
       effective type of the object,
    -- an aggregate or union type that includes one of the aforementioned types among its
       members (including, recursively, a member of a subaggregate or contained union), or
    -- a character type.

Footnote 88) The intent of this list is to specify those circumstances in which an object may or may not be aliased.

Кстати, я похоже там выше яростно гнал. Через char* таки можно по данным лазать.

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

Нигде. Strict aliasing - это интерпертация стандарта.

Смотри выше. Хотя самой фразы strict aliasing в стандарте нет, это да.

Сам стандарт, кажется, вообще не предназначен для чтения людьми.

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

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

Кстати, я похоже там выше яростно гнал. Через char* таки можно по данным лазать

Да, только теперь осталось понять, что такое «лазать», и что такое «данные».

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

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

Сгенерированная ботами бессмыслица тоже «легко читается». Разница заключается в том, что написанный человеком для человека текст содержит смысл.

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

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

Не знаю, с кем у вас тут такой разговор был.

Все старые компиляторы Си и новые с отключенными strict aliasing корректно выполняют код. Давай думать, каким образом при отключении strict aliasing не он самый оказывается виновником проблемы?

Давай лучше ты покажешь в том коде конкретное выражение и конкретное подвыражение в нём, при вычислении которого происходит нарушение strict aliasing rule.

anonymous
()
Ответ на: комментарий от shkolnick-kun

strict aliasing rule это неформальное название для параграфа, процитированного в В чём смысл делать так_s постфиксить_t ? (комментарий) (и аналогичных параграфов в других версиях стандарта C и в C++).

В стандарте C++ нет аббревиатуры SFINAE. Что же теперь, не пользоваться ею, а перечислять то, что ею называют?

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

Ну так решил не только я. Так ещё авторы gcc решили. А почему оно там не будет нарушаться? Это два разных типа. Тот факт, что их определения одинаковы, не делает их одним и тем же типом.

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

Ну так решил не только я. Так ещё авторы gcc решили.

Ты не кивай на других.

А почему оно там не будет нарушаться? Это два разных типа.

Правило strict aliasing rule говорит «An object shall have its stored value accessed only by an lvalue expression that has one of the following types». access это чтение или запись, см. http://port70.net/~nsz/c/c11/n1570.html#3.1p1

access
<execution-time action> to read or modify the value of an object

Покажи в том коде конкретное lvalue, которым (через которое) производится доступ с нарушением правил strict aliasing.

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

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

#include <stdio.h>
 
typedef struct { int i1; } s1;
typedef struct { int i2; } s2;
 
void f(s1 *s1p, s2 *s2p) {
  (*s1p).i1 = 2;
  (*s2p).i2 = 3;
  printf("%i\n", (*s1p).i1);
}
 
int main() {
  s1 s = {.i1 = 1};
  f(&s, (s2 *)&s);
}

Так очевиднее?

hateyoufeel ★★★★★
()
Последнее исправление: hateyoufeel (всего исправлений: 1)
Ответ на: комментарий от shkolnick-kun

Поздно. Уже даже пример был

a union

Про такое и вопросов нет

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

Так очевиднее?

Забавно, конечно, что ты не хочешь указать на конкретное выражение.

Ну ладно, попробую догадаться за тебя. По-твоему, в (*s2p).i2 = 3, а точнее в *s2p (или (*s2p)) происходит нарушение strict aliasing rule, т.к. *s2p это lvalue с типом s2, а объект, к которому якобы происходит доступ, имеет тип s1.

Но проблема в том, что доступа-то не происходит. И не потому, что компилятор его соптимизировал. Можно проверить на volatile-типах, т.к. доступ к volatile-объектам должен происходить так же, как при вычислении в абстрактной машине C. Соптимизировать нельзя.

volatile struct S { int i; char c; float f; } s;

int main()
{
    struct S s2;
    s2 = s;     // есть доступ через `s` lvalue
    s2.i = s.i; // нет доступа через `s`, есть только через `s.i` lvalue
}

https://godbolt.org/z/W4Pyty вот, можешь убедиться.

Таким образом, в выражениях вида E1.name, через E1 lvalue не происходит доступа, strict aliasing rule к случаю когда доступа нет неприменим.

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

По-твоему, в (*s2p).i2 = 3, а точнее в *s2p (или (*s2p)) происходит нарушение strict aliasing rule, т.к. *s2p это lvalue с типом s2, а объект, к которому якобы происходит доступ, имеет тип s1.

Молодец!

Таким образом, в выражениях вида E1.name, через E1 lvalue не происходит доступа, strict aliasing rule к случаю когда доступа нет неприменим.

Это ты можешь авторам GCC рассказать. Тикет в их багзилле уже открыл?

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

Это ты можешь авторам GCC рассказать. Тикет в их багзилле уже открыл?

Так я тут при чём? Это тебе надо открывать тикет, раз ты веруешь, что в s.i, при вычислении подвыражения слева от точки происходит доступ. А для volatile-структуры GCC, сука такая, не генерирует асм-кода, читающего всю структуру.

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

Так какое из поведений, по-твоему, правильное? Как ты объяснишь поведение кода, который я привёл, кроме как через undefined behaviour из-за нарушения strict aliasing rule?

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

Как ты объяснишь поведение кода, который я привёл, кроме как через undefined behaviour из-за нарушения strict aliasing rule?

Лучше расскажы, как ты приплетёшь нарушение strict aliasing rule в C++ для твоего примера, с учётом того, что сказано в http://eel.is/c++draft/defns.access#sentence-2 и http://eel.is/c++draft/basic.lval#11.sentence-3

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

An object shall have its …

… shall …

Не вижу здесь слова «must»

Н и наличие пунктов 6.5.2.3 - 6 и 6.7.2.1 - 15

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

shkolnick-kun ★★★★★
()
Последнее исправление: shkolnick-kun (всего исправлений: 5)
Ответ на: комментарий от shkolnick-kun

Не вижу здесь слова «must»

По правилам написания стандартов ISO, в нормативном тексте употребляется «shall».

anonymous
()
Ответ на: комментарий от shkolnick-kun

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

При условии, что в этих первых N есть тип с самым «длинным» хранилищем.

понятно, что если у нас

typedef struct {uint8_t a; uint16_t b;} x;
//и
typedef struct {uint8_t a; uint64_t b;} y;

То результат доступа к a может быть немного неожиданным…

shkolnick-kun ★★★★★
()
Последнее исправление: shkolnick-kun (всего исправлений: 2)
Ответ на: комментарий от shkolnick-kun

Не вижу здесь слова «must»

Дополню более развёрнуто.

ISO/IEC Directives, Part 2 Principles and rules for the structure and drafting of ISO and IEC documents

3.3.3
requirement
expression, in the content of a document (3.1.1), that conveys objectively verifiable criteria to be fulfilled and from which no deviation is permitted if conformance with the document is to be claimed
Note 1 to entry: Requirements are expressed using the verbal forms specified in Table 3.

ISO/IEC Directives, Part 2 Principles and rules for the structure and drafting of ISO and IEC documents

Table 3 — Requirement
Preferred verbal form
shall
...
Do not use “must” as an alternative for “shall”. This avoids confusion between the requirements of a document and external constraints (see 7.6).
Do not use “may not” instead of “shall not” to express a prohibition.

«must» может употребляться в нормативном тексте, но вряд ли такому случаю есть место в стандарте C. Так или иначе, в параграфе про strict aliasing должен быть «shall».

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