вопрос про стиль кодирования
От: ilya123 Россия  
Дата: 07.01.03 09:55
Оценка:
В примерах от MS все переменные объявляются в начале функции и используется NULL. Хотя Страуструп советует объявлять переменные только перед использованием и вместо NULL использовать 0. Кто прав? Интересует этот вопрос применительно к кодированию под Windows.
Re: вопрос про стиль кодирования
От: peterbes Россия  
Дата: 07.01.03 11:12
Оценка:
Здравствуйте, ilya123, Вы писали:

I>В примерах от MS все переменные объявляются в начале функции и используется NULL. Хотя Страуструп советует объявлять переменные только перед использованием и вместо NULL использовать 0. Кто прав? Интересует этот вопрос применительно к кодированию под Windows.



NULL это традиция.Нулем он и определен — а в чем проблема?

#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif

Насчет, сразу или после определять переменную, так это дело практики или личных присрастий, делайте как Вам угодно, только делайте это, того требует элементарная аккуратность. Первый вариант мне нравитсться больше.
Re: вопрос про стиль кодирования
От: MaximE Великобритания  
Дата: 07.01.03 11:21
Оценка:
Здравствуйте, ilya123, Вы писали:

I>В примерах от MS все переменные объявляются в начале функции и используется NULL. Хотя Страуструп советует объявлять переменные только перед использованием и вместо NULL использовать 0. Кто прав? Интересует этот вопрос применительно к кодированию под Windows.


Обрати внимание: большинство примеров по API написаны на С, а не на C++.
Re: вопрос про стиль кодирования
От: Xentrax Россия http://www.lanovets.ru
Дата: 07.01.03 12:59
Оценка:
Здравствуйте, ilya123, Вы писали:

I>В примерах от MS все переменные объявляются в начале функции и используется NULL. Хотя Страуструп советует объявлять переменные только перед использованием и вместо NULL использовать 0. Кто прав? Интересует этот вопрос применительно к кодированию под Windows.


Про C уже сказали. Я использую 0 вместо NULL — мне так больше нравится.

Что касается объявления переменных перед использованием, то лучше действительно объявлять перед использованием. "Расстояние" (в SLOC обычно) между объявлением переменной и первым ее использованием назвается "пробегом" и учитывается в некоторых метриках "поддерживаемости" и "надежности" кода. Чем меньше суммарный пробег по программе, тем лучше.
Re[2]: вопрос про стиль кодирования
От: Кодт Россия  
Дата: 07.01.03 13:21
Оценка:
Здравствуйте, Xentrax, Вы писали:

X>Про C уже сказали. Я использую 0 вместо NULL — мне так больше нравится.


Дело вкуса, хотя NULL — это указатель, а 0 — это в том числе и число.
Просто чтобы не было путаницы...

X>Что касается объявления переменных перед использованием, то лучше действительно объявлять перед использованием. "Расстояние" (в SLOC обычно) между объявлением переменной и первым ее использованием назвается "пробегом" и учитывается в некоторых метриках "поддерживаемости" и "надежности" кода. Чем меньше суммарный пробег по программе, тем лучше.


А также от конструирования до первого использования и от последнего использования до деструирования.
Особенно это существенно для объектов.
Перекуём баги на фичи!
Re: вопрос про стиль кодирования
От: WolfHound  
Дата: 07.01.03 15:42
Оценка: 3 (1)
Здравствуйте, ilya123, Вы писали:

I>В примерах от MS все переменные объявляются в начале функции и используется NULL. Хотя Страуструп советует объявлять переменные только перед использованием и вместо NULL использовать 0. Кто прав? Интересует этот вопрос применительно к кодированию под Windows.


Я считаю что надо обьявлять перед использованием и по возможности инициализировать осмысленно(не 0).
те
    Type* ptr=new Type();

а не
    Type* ptr=0;
    ptr=new Type();

Если мы говорим о С++ то надо обязательно заворачивать в умный указатель
    T_SmartPtr<Type> ptr=new Type();

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

Вариант но можно забыть удалить обьект
Type* createType()
{
    Type* ptr=new Type()
    //......
    return ptr;
}


А вот так обьект будет _гарантировано_ удален по окончанию использования
T_SmartRefPtr<Type> createType()
{
    T_SmartRefPtr<Type> ptr=new Type()
    //......
    return ptr;
}


Ессно нельзя сохранить указатель во внешних переменных если не гарантируется что обертка не бедет разрушина
    Type* ptr=0;
    {
        T_SmartPtr<Type> sptr=new Type();
        ptr=sptr;
    }//здесь обьект будет удален и ptr стенет инвалидным


void foo(Type* p)
{
    //.....
}
void bar()
{
    T_SmartPtr<Type> ptr=new Type();
    foo(ptr);
}//здесь обьект будет удален после выхода из foo и проблем не возникнет


Но я рекомендую T_SmartRefPtr ибо если вдруг реализация foo будет изменена и она сохранит указатель то AV неизбежин.
//Передача по ссылке ибо T_SmartRefPtr хотя и легок но лишние копирование не нужно
void foo(const T_SmartRefPtr<Type>& p)
{
    //.....
}
void bar()
{
    T_SmartRefPtr<Type> ptr=new Type();
    foo(ptr);
}//теперь если foo сохранит указатель то сдесь удаление обьекта не произойдет


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

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

тагже нужно не допускать конструкции типа
    T_SmartRefPtr<Type> sptr1=new Type();
    Type* ptr=sptr;
    T_SmartRefPtr<Type> sptr2=ptr;

ибо гарантировано будет попытка дважды удалить этот обьект и наверняка попытка доступа к мертвому обьекту.

ессно так тоже ни чего хорошего не получится
    T_SmartRefPtr<Type> ptr=new Type();
    delete ptr;


Заключение: Не смотря на появление новых опасностей рекомендую к использованию ибо наступить на них много сложнее чем забыть освободить память или закрыть фаил.
... << RSDN@Home 1.0 beta 4 >>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Re[2]: вопрос про стиль кодирования
От: peterbes Россия  
Дата: 07.01.03 17:35
Оценка:
Все чересчур сурово смарт-указатели, приведения указателей и прочее. Мне не нравиться ... а вообще все здорово! До Элджера я все это видел у Borland-а в BC4.0. Инклюд потешно назывался <owl/pointer.h>, я тогда все понять не мог почему он так называется. В то время ребята Borland-a творили чудеса.Когда понял как работают смарт-указатели (это их сейчас так называют/умных книжек было мало) ходил в одурении месяца два, засовывал их во все места. Сегодня это как мат двумя слонами — скучно и банально.


Удачи, коллеги.
Re[3]: вопрос про стиль кодирования
От: UgN  
Дата: 08.01.03 07:45
Оценка:
Здравствуйте, Кодт, Вы писали:

К>А также от конструирования до первого использования и от последнего использования до деструирования.

К>Особенно это существенно для объектов.

А вот так (не)хорошо делать?



void func()
{
  
  {
     ClassA a;
     a.method();
  }

  {
     ClassB b;
     b.method();
  }

}



Таким образом ведь можно существенно уменьшить расстояние от конструирования до деструктирования?
Re[2]: вопрос про стиль кодирования
От: retalik www.airbandits.com/
Дата: 08.01.03 08:19
Оценка:
Здравствуйте, WolfHound, Вы писали:

WH>Если мы говорим о С++ то надо обязательно заворачивать в умный указатель

WH>
WH>    T_SmartPtr<Type> ptr=new Type();
WH>


Речь идет о какой-то конкретной библиотеке или T_SmartPtr — обобщенное имя?
Просто мне хочется в проекте использовать смарт-указатель (не COM) попрямее (не такой, как std::auto_ptr).
Пока нравится только boost::shared_ptr. Еще есть идеи?
Успехов,
Виталий.
Re[4]: вопрос про стиль кодирования
От: Кодт Россия  
Дата: 08.01.03 08:54
Оценка: 3 (1)
Здравствуйте, UgN, Вы писали:

К>>А также от конструирования до первого использования и от последнего использования до деструирования.

К>>Особенно это существенно для объектов.

UgN>А вот так (не)хорошо делать?

UgN>void func()
UgN>{
UgN>  
UgN>  {
UgN>     ClassA a;
UgN>     a.method();
UgN>  }

UgN>  {
UgN>     ClassB b;
UgN>     b.method();
UgN>  }

UgN>}

UgN>Таким образом ведь можно существенно уменьшить расстояние от конструирования до деструктирования?

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

Если это (умный) указатель / хэндл ресурса — то хотя бы обнулять его после использования (что вызовет освобождение объекта/ресурса сразу же).
Перекуём баги на фичи!
Re[4]: вопрос про стиль кодирования
От: Владик Россия  
Дата: 08.01.03 09:44
Оценка:
Здравствуйте, UgN, Вы писали:

UgN>А вот так (не)хорошо делать?


Почему бы тогда не написать:

     ClassA().method();
     ClassB().method();


А если использование каждого из классов более сложное, то вынести работу с ними в отдельные функции?
Как все запущенно...
Re[5]: вопрос про стиль кодирования
От: UgN  
Дата: 08.01.03 09:50
Оценка:
Здравствуйте, Владик, Вы писали:

В>Почему бы тогда не написать:


В>
В>     ClassA().method();
В>     ClassB().method();
В>


Ну а если не один метод вызывается, а несколько?

В>А если использование каждого из классов более сложное, то вынести работу с ними в отдельные функции?


А если не настолько сложное, чтобы выносить в отдельную функцию?
Re[5]: вопрос про стиль кодирования
От: UgN  
Дата: 08.01.03 09:52
Оценка:
Ну а само искусственное введение блоков {} ничем побочным не грозит?
Компилятор обязан их все учитывать?
Re[6]: вопрос про стиль кодирования
От: Владик Россия  
Дата: 08.01.03 09:56
Оценка:
Здравствуйте, UgN, Вы писали:

В>>А если использование каждого из классов более сложное, то вынести работу с ними в отдельные функции?

UgN>А если не настолько сложное, чтобы выносить в отдельную функцию?

Значит использовать {}.
Как все запущенно...
Re[3]: вопрос про стиль кодирования
От: ilya123 Россия  
Дата: 08.01.03 10:02
Оценка: -1
Здравствуйте, retalik, Вы писали:

WH>>Если мы говорим о С++ то надо обязательно заворачивать в умный указатель

WH>>
WH>>    T_SmartPtr<Type> ptr=new Type();
WH>>


R>Речь идет о какой-то конкретной библиотеке или T_SmartPtr — обобщенное имя?

R>Просто мне хочется в проекте использовать смарт-указатель (не COM) попрямее (не такой, как std::auto_ptr).
R>Пока нравится только boost::shared_ptr. Еще есть идеи?

Я для себя написал и давно использую такие макросы:

class CDeleteOnExit
{
    void* m_p;
    bool m_bArray;
public:
    CDeleteOnExit(void* _p, bool _bArray = false ): m_p(_p), m_bArray(_bArray) {};
    ~CDeleteOnExit()
    {
        if(m_bArray) delete[](m_p);
        else delete(m_p);
    }
};
#define DELETE_ON_EXIT(x) CDeleteOnExit _CDeleteOnExit##x(x);
#define DELETE_ARRAY_ON_EXIT(x) CDeleteOnExit _CDeleteOnExit##x(x, true);
Re: вопрос про стиль кодирования
От: Apapa Россия  
Дата: 08.01.03 10:06
Оценка: 8 (1)
Привет, ilya123!

I>В примерах от MS все переменные объявляются в начале функции и используется NULL. Хотя Страуструп советует объявлять переменные только перед использованием и вместо NULL использовать 0. Кто прав? Интересует этот вопрос применительно к кодированию под Windows.


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

Для себя я давным-давно сформулировал одно простое правило, успешно корректирующее мой собственный стиль написания программ: программа пишется для того, чтобы она работала.

Первое, что из этого вытекает, это то, что код должен быть удобочитаемым. В этом и только в этом случае его можно будет всегда успешно подправить или дописать, найти ошибку и т.п. Поэтому для инициализации указателей нулем я обычно всегда пишу явный 0, указывая явным образом в скобках тип, хотя стандарт этого делать не требует: (type *)0.

Что касается переменных, то мое глубокое убеждение, точнее жизненный опыт, говорит о том, что описание переменных по ходу текста функции — одно из главных преимуществ C++ перед Паскалем. Почему? Очень просто:
1. Локализация переменных. Возможность использования переменной в явном контексте функции, избегая ее использования (в том числе и случайного — сколько раз такое было!) вне конкретного блока, цикла и т.д.
2. Оптимизация. Возможность создания и уничтожения классов, структур, их экземпляров, переменных и т.п. в нужных местах, т.е. экономия памяти, времени, оптимизация и т.п.
3. Удобочитаемость. Если переменная i используется только в цикле, так давайте использовать ее только там. В пользу этого говорят оба предыдущих пункта. Кроме того, в начале функции рекомендуется собирать только те объявления, которые наиболее существенны для всей функции, отражают ее смысл и вариант реализации. Какой автор театральной пьесы будет вписывать в "действующие лица" всех собак и кошек, участвующих в действии, или молчаливого кучера?

Одним словом, при разумном использовании возможностей C++ код становится более ясным для понимания. Так почему этим не пользоваться?


Здесь могла бы быть Ваша реклама!
Re[3]: вопрос про стиль кодирования
От: WolfHound  
Дата: 08.01.03 11:52
Оценка:
Здравствуйте, retalik, Вы писали:

R>Речь идет о какой-то конкретной библиотеке или T_SmartPtr — обобщенное имя?

Это обобщенное имя.

R>Еще есть идеи?

Я их сам пишу.

Вот такие мелочи экономят кучу нервов
template <class T_C>class T_CoAutoArr
{
    T_C* m_Arr;
public:
    T_CoAutoArr(int count)
        :m_Arr(0)
    {
        m_Arr=(T_C*)CoTaskMemAlloc(count*sizeof(T_C));
    }
    ~T_CoAutoArr()
    {
        CoTaskMemFree(m_Arr);
        m_Arr=0;
    }
    operator T_C*()
    {
        return m_Arr;
    }
    T_C* Detach()
    {
        T_C* t=m_Arr;
        m_Arr=0;
        return t;
    }
};

//В отличии от CComPtr можно засунуть в slt'ные контейнеры
template<class T_C>class T_CoPtr
{
    T_C* m_Ptr;
public:
    T_CoPtr()
        :m_Ptr(0)
    {}
    T_CoPtr(const T_CoPtr& o)
        :m_Ptr(o.m_Ptr)
    {
        if(m_Ptr)m_Ptr->AddRef();
    }
    T_CoPtr(T_C* o)
        :m_Ptr(o)
    {
        if(m_Ptr)m_Ptr->AddRef();
    }
    T_CoPtr& operator=(const T_CoPtr& o)
    {
        if(this==&o)return *this;
        if(m_Ptr==o.m_Ptr)return *this;
        if(m_Ptr)m_Ptr->Release();
        m_Ptr=o.m_Ptr;
        if(m_Ptr)m_Ptr->AddRef();
        return *this;
    }
    T_CoPtr& operator=(const T_C* o)
    {
        if(m_Ptr==o)return *this;
        if(m_Ptr)m_Ptr->Release();
        m_Ptr=o;
        if(m_Ptr)m_Ptr->AddRef();
        return *this;
    }
    ~T_CoPtr()
    {
        if(m_Ptr)m_Ptr->Release();
    }
    T_C* ClonePtr()
    {
        if(m_Ptr)m_Ptr->AddRef();
        return m_Ptr;
    }
    T_C* operator ->()
    {
        return m_Ptr;
    }
    operator T_C*()
    {
        return m_Ptr;
    }
};

Пример использования
STDMETHODIMP CSRCOMMOPCGroup::AddItems( 
/* [in] */                        DWORD dwCount,
/* [size_is][in] */                OPCITEMDEF *pItemArray,
/* [size_is][size_is][out] */    OPCITEMRESULT **ppAddResults,
/* [size_is][size_is][out] */    HRESULT **ppErrors
)
{
    LogPrint(L"IOPCItemMgt::AddItems\n");
    if(m_Public)        return OPC_E_PUBLIC;
    if(dwCount<=0)        return E_INVALIDARG;
    if(!pItemArray)        return E_INVALIDARG;
    if(!ppAddResults)    return E_INVALIDARG;
    if(!ppErrors)        return E_INVALIDARG;
    *ppAddResults=0;
    *ppErrors=0;
    T_CoAutoArr<HRESULT>        hr(dwCount);
    T_CoAutoArr<OPCITEMRESULT>    ir(dwCount);
    if(!hr||!ir)return E_OUTOFMEMORY;
    UINT fails=0;
    {//Sync
        C_SyncHelper lockDItem(g_DItemSync, C_SyncHelper::StateWrite);
        C_SyncHelper lockThis(this, C_SyncHelper::StateWrite);
        for(UINT i=0;i<dwCount;i++)
        {
            T_CoPtr<COPCGroupItem> p=new COPCGroupItem(this);
            int h=m_Items.Include(p);
            hr[i]=p->Init(&pItemArray[i], &ir[i], h);
            if(FAILED(hr[i]))
            {
                fails++;
                m_Items.Exclude(h);
                ir[i].dwAccessRights        =0;
                ir[i].dwBlobSize            =0;
                ir[i].hServer                =g_ItemIndex.end();
                ir[i].pBlob                    =0;
                ir[i].vtCanonicalDataType    =VT_ERROR;
                ir[i].wReserved                =0;
            }
        }
    }
    *ppAddResults=ir.Detach();
    *ppErrors=hr.Detach();
    if(fails)    return S_FALSE;
    else        return S_OK;
}

C_SyncHelper и не видимый сдесь C_SyncBase тоже моего изготовления.


Вот таже функчия от неизвестного программиста
STDMETHODIMP CIOPCItemMgt::AddItems( 
    DWORD            dwNumItems,
    OPCITEMDEF     * pItemArray,
    OPCITEMRESULT ** ppAddResults,
    HRESULT       ** ppErrors
    )
{
    unsigned int    i;
    OPCITEMRESULT *ir;
    HRESULT *hr;
    SYSTEMTIME SystemTime;
    BOOL    ok = TRUE;
    COPCItem    *pItem;

    GetApp()->DisplayEvent ("IOPCItemMgt::AddItems");

    // First - allocate memory for the result array(s)
    //
    ir = (OPCITEMRESULT*)GetApp()->pIMalloc->Alloc(sizeof(OPCITEMRESULT) * dwNumItems);    //acc001
    if(ir == NULL)
        {
        *ppAddResults = NULL;
        *ppErrors = NULL;
        return (E_OUTOFMEMORY);
        }

    *ppAddResults = ir;

    hr = (HRESULT*) GetApp()->pIMalloc->Alloc(sizeof(HRESULT) *dwNumItems);        //acc001
    if (hr == NULL)
        {
        GetApp()->pIMalloc->Free(ir);    
        *ppAddResults = NULL;
        *ppErrors = NULL;
        return (E_OUTOFMEMORY);
        }

    *ppErrors = hr;

    // Now for each item... 
    //
    for(i=0; i<dwNumItems; i++)
        {
        hr[i] = GetApp()->ValidateItem (&pItemArray[i], &ir[i]);

        if (hr[i] == S_OK)
            {//Как это работает одному ... известно.
            // Create a new OPCItem
            pItem = new (COPCItem);//А если ехепшен вылетит?

            pItem->Name = pItemArray[i].szItemID;
            pItem->AccessPath = pItemArray[i].szAccessPath;
            pItem->IsActive = pItemArray[i].bActive;
            pItem->NativeDataType = VT_R4;
            pItem->RequestedDataType = pItemArray[i].vtRequestedDataType;
            pItem->Value = 0.0;
            pItem->Quality = OPC_QUALITY_UNCERTAIN;
            GetSystemTime(&SystemTime);        // Get current UTC Time
            SystemTimeToFileTime(&SystemTime, &(pItem->TimeStamp)); // and store it
//RTFM CoFileTimeNow
            pItem->ClientHandle = pItemArray[i].hClient;
            // Server Handle will be assigned by Group
            pGroup->AddItem(pItem);

            // Set ITEM RESULT
            ir->hServer = pItem->SvrHandle;
            ir->vtCanonicalDataType = pItem->NativeDataType;
            ir->dwAccessRights = OPC_WRITEABLE | OPC_READABLE;
            ir->pBlob = NULL;
            ir->dwBlobSize = 0;
            }

        }

    for (i=0; i<dwNumItems; i++)
        {
        if (hr[i] != S_OK)
            return (E_FAIL);    // Some Items could not be added//это не соответствие стандарту OPC
        }
    
    return (S_OK);

}
... << RSDN@Home 1.0 beta 4 >>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Re[4]: вопрос про стиль кодирования
От: a1ex_k Россия  
Дата: 08.01.03 12:05
Оценка:
Здравствуйте, ilya123, Вы писали:

I>#define DELETE_ON_EXIT(x) CDeleteOnExit _CDeleteOnExit##x(x);

I>#define DELETE_ARRAY_ON_EXIT(x) CDeleteOnExit _CDeleteOnExit##x(x, true);

Эти макросы не вызывают деструктор для объекта. Вас это не беспокоит?
См. ниже программу

class Foo
{
    public:
        Foo() { cout<<"Foo::Foo()"<<endl; };
        ~Foo() { cout<<"Foo::~Foo()"<<endl; };
};

void f1()
{
    cout<<"f1()"<<endl;
    Foo* p = new Foo;
    DELETE_ON_EXIT(p);
}

void f2()
{
    cout<<"f2()"<<endl;
    Foo* p = new Foo;
    delete p;
}


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

    f1();
    f2();
 
    return 0;
}


Результат работы программы:

f1()
Foo::Foo()
f2()
Foo::Foo()
Foo::~Foo()
Press any key to continue
Re[4]: вопрос про стиль кодирования
От: Можаев Михаил Россия www.mozhay.chat.ru
Дата: 08.01.03 12:19
Оценка:
Здравствуйте, ilya123, Вы писали:

I>Я для себя написал и давно использую такие макросы:


I>
I>class CDeleteOnExit
I>{
I>    void* m_p;
I>    bool m_bArray;
I>public:
I>    CDeleteOnExit(void* _p, bool _bArray = false ): m_p(_p), m_bArray(_bArray) {};
I>    ~CDeleteOnExit()
I>    {
I>        if(m_bArray) delete[](m_p);
I>        else delete(m_p);
I>    }
I>};
I>#define DELETE_ON_EXIT(x) CDeleteOnExit _CDeleteOnExit##x(x);
I>#define DELETE_ARRAY_ON_EXIT(x) CDeleteOnExit _CDeleteOnExit##x(x, true);
I>


И что же получается? Мы передаем в конструктор указатель CMyObject*, там он сохраняется в void* и потом удаляется, как void*.
Разве можно это назвать правильным?
... << RSDN@Home 1.0 beta 4 >>
Re[2]: Работоспособность неудобочитаемого кода
От: Vi2 Удмуртия http://www.adem.ru
Дата: 08.01.03 13:11
Оценка:
Здравствуйте, Apapa, Вы писали:

A>...
A>Для себя я давным-давно сформулировал одно простое правило, успешно корректирующее мой собственный стиль написания программ: программа пишется для того, чтобы она работала.

A>Первое, что из этого вытекает, это то, что код должен быть удобочитаемым...

Увы! Из первого утверждения второе не "вытекает". Даже, более того, именно работоспособность неудобочитаемого (для человека) кода и усугубляет проблему. Ибо компилятору до лампочки удобочитаемость, или структурированность, или соответствие каким-то корпоративным правилам, т.е. то, что выходит за рамки синтаксиса языка.

Поэтому лучше говорить:"Код программы пишется для чтения его в первую очередь человеком, а потом только компилятором".
Vita
Выше головы не прыгнешь, ниже земли не упадешь, дальше границы не убежишь! © КВН НГУ
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.