Автор Тема: C++ инициализация ссылок  (Прочитано 57899 раз)

valexey_u

  • Hero Member
  • *****
  • Сообщений: 3013
    • Просмотр профиля
Re: C++ инициализация ссылок
« Ответ #75 : Март 21, 2013, 08:21:41 pm »
Потому что это один из банальных моментов, который оптимизируется на ура.
Ведь локальная i - это что-то из серии EBP-4. То есть адреса, которые известны на этапе компиляции, разумеется не используются. А вот если у вас будет возврат ссылки из функции, то гляньте код. Там будет адрес.
Да, и я не знаю что такое EBP-4. Скажем в процессорах с которыми я имею дело, такой штуки в принципе нет. :-)
Y = λf.(λx.f (x x)) (λx.f (x x))

Berserker

  • Sr. Member
  • ****
  • Сообщений: 254
    • Просмотр профиля
Re: C++ инициализация ссылок
« Ответ #76 : Март 21, 2013, 08:26:17 pm »
Адрес локальной переменной для текущей процедуры. Не суть. Была бы глобальная переменная по адресу 0x400000, для ссылки точно так же ничего бы не генерировалось, так как вместо (*p) имеем (*((T*) 0x400000)).

Цитировать
Покажи мне тут, пожалуйста, где у нас ссылка в сгенерированном коде :-)
Эм, процедура встроилась (inlined), затем то, что указано выше. Ровно так же можно и с указателем оптимизировать, если агрессивно.

Алексей, Вы как будто не поняли моих слов. Ссылки при известном строгом адресе оптимизируются. Но это простые случаи. Когда же имеем код общего вида, где ссылка устанавливается в конструкторе или функция возвращает созданный объект, то оптимизации уже невозможны и адрес приходится хранить. А по поводу оптимизаций, так даже неиспользуемые переменные можно выкидывать, функции встраивать, циклы раскручивать. Только о чём это говорит кроме факта оптимизации?
« Последнее редактирование: Март 21, 2013, 08:31:28 pm от Berserker »

vlad

  • Hero Member
  • *****
  • Сообщений: 1391
    • Просмотр профиля
Re: C++ инициализация ссылок
« Ответ #77 : Март 21, 2013, 08:29:59 pm »
Вообще-то я думаю в терминах реального железа, ассемблера и конструкций С++.

В терминах реального железа и ассемблера ссылка в большинстве случае будет выглядеть как ячейка с адресом. И этим идентична указателю. С этим никто не спорит. Но как конструкция С++ она довольно сильно отличается от указателя. Этими отличиями можно пользоваться во благо (я там выше писал, почему ссылки полезны). В том числе и тем, что ссылка инициализируется один раз.

Цитировать
Ссылка не может содержать NULL и приведенный код невалидный (UB в момент "*p"). Да, компиляция без предупреждений - частный случай UB.
Не может в каком мире?

В мире корректных C++ программ. Код разыменования нулевого указателя не корректен с точки зрения языка. Потому что это явно прописанный в стандарте UB. Т.е., ты теоретически можешь найти компилятор, который будет проверять в рантайме разыменование нулевого указателя и форматировать жесткий диск в этом случае. И такой компилятор при этом может соответсвовать стандарту С++, а твоя программа с таким разыменованием - точно нет.
Почему такая проверка не делается в существующих популярных реализация тоже понятно - эффективность с одной стороны и куча говнокода с другой.

Не Вы ли писали, что ссылка гарантирована не NULL, не мёртвая и не висячая? Какой смысл писать то, что не соответствует действительности?

Она гарантировано не NULL если в программе нет разыменования нулевых указателей. Т.е., в корректной С++ программе. Про мертвые/висячие я вообще ничего не говорил.

А теперь попробуйте сделать то же самое в Обероне. Чтобы VAR-параметр вылетал при обращении к нему или содержал мусор.

В обероне сематника VAR параметра отличается от семантики ссылки в С++. В частности в обероне ты не может вернуть VAR или положить его как поле структуры.

1) Страуструп всё-таки оставил один и тот же синтаксис и для присваивания значения переменной и для инициализации ссылки.
2) И первый пункт разрулить и убрать неоднозначность можно было бы при помощи введения ключевого слова или нового оператора (например, ===).

Мне кажется, что оно просто того не стоит. Обратимся к примеру.

Элементарно. Мне нужно обменять адреса двух ссылок. То есть swap со скоростью указателей.

Зачем? Я не издеваюсь. Просто нет такой задачи самой по себе.

В парсере PrevLexem и ThisLexem постоянно обмениваются.

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

valexey_u

  • Hero Member
  • *****
  • Сообщений: 3013
    • Просмотр профиля
Re: C++ инициализация ссылок
« Ответ #78 : Март 21, 2013, 08:38:13 pm »
Адрес локальной переменной для текущей процедуры. Не суть. Была бы глобальная переменная по адресу 0x400000, для ссылки точно так же ничего бы не генерировалось, так как вместо (*p) имеем (*((T*) 0x400000)).
У меня там как раз глобальная переменная ссылка на которую возвращается через функцию которая вызывается из другой функции и "присваивается" локальной ссылке.

Еще раз - ссылка это алиас. Это не first-class citizen. Поэтому ссылка штука во-первых более безопасная чем указатель, во-вторых более эффективная и местами более фичастая (ибо позволяет например дать имя результату какого-либо выражения не заводя новую переменную).

Иногда ссылки да, реализуются через адреса. Но реже чем указатели. Семантика ссылок и указателей РАЗНАЯ и ссылки не являются простым синтаксическим сахаром над семантикой указателей.

Если нужна семантика указателей, и ссылка не годится, то следует (в подавляющем большенстве случаев)... не пользоваться указателями, а воспользоваться умными указателями. Голые указатели - они только для крайних случаев (например для реализации умных указателей, для реализации своих менеджеров памяти и так далее).
Y = λf.(λx.f (x x)) (λx.f (x x))

vlad

  • Hero Member
  • *****
  • Сообщений: 1391
    • Просмотр профиля
Re: C++ инициализация ссылок
« Ответ #79 : Март 21, 2013, 08:41:57 pm »
Еще к вопросу об UB, говнокоде и отсутвии нормальной диагностики разыменования нулевых указателей. Вот метод весьма популярного класса весьма популярной библиотеки весьма промышленной конторы M$:
HWND CWnd::GetSafeHwnd() const { return this == NULL ? NULL : m_hWnd; }

Существование этого кода говорит лишь о том, что код гавно, а автор мудак. А не о том, что this в С++  может быть нулем.

valexey_u

  • Hero Member
  • *****
  • Сообщений: 3013
    • Просмотр профиля
Re: C++ инициализация ссылок
« Ответ #80 : Март 21, 2013, 08:43:15 pm »
Еще к вопросу об UB, говнокоде и отсутвии нормальной диагностики разыменования нулевых указателей. Вот метод весьма популярного класса весьма популярной библиотеки весьма промышленной конторы M$:
HWND CWnd::GetSafeHwnd() const { return this == NULL ? NULL : m_hWnd; }

Существование этого кода говорит лишь о том, что код гавно, а автор мудак. А не о том, что this в С++  может быть нулем.
Пахнуло MFC :-)
Y = λf.(λx.f (x x)) (λx.f (x x))

valexey_u

  • Hero Member
  • *****
  • Сообщений: 3013
    • Просмотр профиля
Re: C++ инициализация ссылок
« Ответ #81 : Март 21, 2013, 08:47:45 pm »
Ну вот еще пример.

#include <stdlib.h>
#include <stdio.h>

int two() {return random()%42;}

int main() {
    const int& ref = two();
    printf("%d\n", ref);
    return 0;
}

Сгенерированный код:
define i32 @_Z3twov() {
  %1 = tail call i32 @random()                    ; <i32> [#uses=1]
  %2 = srem i32 %1, 42                            ; <i32> [#uses=1]
  ret i32 %2
}

declare i32 @random()

define i32 @main() {
  %1 = tail call i32 @random()                    ; <i32> [#uses=1]
  %2 = srem i32 %1, 42                            ; <i32> [#uses=1]
  %3 = tail call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([4 x i8]* @.str, i32 0, i32 0), i32 %2) ; <i32> [#uses=0]
  ret i32 0
}
Задача - найти адрес которым пользуется ref, ну и саму ref заодно :-)
Y = λf.(λx.f (x x)) (λx.f (x x))

Berserker

  • Sr. Member
  • ****
  • Сообщений: 254
    • Просмотр профиля
Re: C++ инициализация ссылок
« Ответ #82 : Март 21, 2013, 08:56:57 pm »
Цитировать
Ну так меняй указатели, в чем проблема-то? Указатели для того и нужны, чтоб указывать на разные штуки во времени. Приведи, плз, более развернеутый пример, пока не очень догоняю.
Цитировать
  class Parser
  {
   protected:
    ...

    Lexem this_lexem_data;
    Lexem prev_lexem_data;
    Lexem *this_lexem;
    Lexem *prev_lexem;
   
   ...
   
    enum kErrorLocation
    {
      kThisLexem,
      kPrevLexem
    };
   
   public:
    Parser ():
      this_lexem(&this_lexem_data),
      prev_lexem(&prev_lexem_data)
    {}
   
    void MarkError (const string &error, kErrorLocation error_location)
    {
      const Lexem &lexem = (error_location == kThisLexem) ? *this_lexem : *prev_lexem;
      log
      (
        error + ". Error in file \"" + config_file_name + "\" on line "
        + ToStr(lexem.line) + " at position " + ToStr(lexem.line_pos),
        "ParseModConfig",
        kMsgError
      );
    }
   
    bool ReadLexem ()
    {
      swap(this_lexem, prev_lexem);
      bool result = vfs::ReadLexem(config, *this_lexem);
     
      if (this_lexem->type == kLexError)
      {
        MarkError(this_lexem->error, kThisLexem);
      }
     
      return result;
    }
...
Можно было бы использовать ссылки. А так приходится довольствоваться синтаксисом "->", "*Lexem" и т.д.

Цитировать
Поэтому ссылка штука во-первых более безопасная чем указатель,
Да нет. В дубовом компиляторе в лоб всегда бы выделялся временный объект. От того, что мёртвый код вычищается, а маленькие функции и циклы и вовсе встраиваются, ничего не меняется. Более того, вы теряете понятие накладных расходов на хранение и разыменование, а в большинстве случаев ссылки всё же не указывают на соседние переменные в кадре стека или глобальные объекты, или указывают, но не так банально.

int some = 13;
int& two() {return some;}

int main() {
    int& ref = two();
    ref = 42;
    return 0;
}

=>

int *ref = &some;
(*ref) = 42

=>

*(&some) = 42

=>

some = 42

Цитировать
Задача - найти адрес которым пользуется ref, ну и саму ref заодно :-)
Функция встроилась. Далее используется ref в качестве аргумента. Больше нигде ref не используется. Поэтому временный результат не сохраняется. А вы вот всё же вызовите вслед функцию из внешней среды, например WinAPI с этим самым ref. И он магическим образом обретёт себе хранилище.

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

Цитировать
Существование этого кода говорит лишь о том, что код гавно, а автор мудак. А не о том, что this в С++  может быть нулем.
Хотел ровно ту же фичу применить для JSon объектов. Вызов метода ведёт к проверке this на null (UB не будет, если класс не содержит виртуальных функций), если null — то это пустое значение, обрабатываемое отдельно. Почему же автор мудак?

vlad

  • Hero Member
  • *****
  • Сообщений: 1391
    • Просмотр профиля
Re: C++ инициализация ссылок
« Ответ #83 : Март 21, 2013, 08:58:05 pm »
При том, что никто не мешал (кроме ООП головного мозга) сделать свободную функцию (или static метод) с требуемой семнтикой и абсолютно корректную с точки зрения C++:
HWND GetSafeHwnd(CWnd* wnd) const { return wnd == NULL ? NULL : wnd->m_hWnd; }

P.S. Не зря Страуструп жалел, что не сделал this ссылкой, а не указателем. Глядишь такого гавна в MFC было бы меньше.

Berserker

  • Sr. Member
  • ****
  • Сообщений: 254
    • Просмотр профиля
Re: C++ инициализация ссылок
« Ответ #84 : Март 21, 2013, 09:09:38 pm »
А проверка на &this == nullptr не спасла бы отца русской демократии в таком случае? :)

vlad

  • Hero Member
  • *****
  • Сообщений: 1391
    • Просмотр профиля
Re: C++ инициализация ссылок
« Ответ #85 : Март 21, 2013, 09:17:39 pm »
Можно было бы использовать ссылки. А так приходится довольствоваться синтаксисом "->", "*Lexem" и т.д.

Декомпозиция и инкапсуляция рулит:
class Lexems
{
public:
    Lexems():
      this_lexem(&this_lexem_data),
      prev_lexem(&prev_lexem_data)
    {}
   
    Lexem& current(){return *this_lexem;}
    Lexem& prev(){return *prev_lexem;}
    void swap(){std::swap(this_lexem, prev_lexem);}
private:
    Lexem this_lexem_data;
    Lexem prev_lexem_data;
    Lexem *this_lexem;
    Lexem *prev_lexem;
};

Все указатели остались во внутренних потрохах Lexems, наружу торчат только ссылки и человеческие методы (возможно swap надо переименовать во что-то более осмысленное). Не? Или lexems.current()/lexems.prev() вызывают нарекания лишними скобочками? Тогда ничем помочь не могу :)

Цитировать
Существование этого кода говорит лишь о том, что код гавно, а автор мудак. А не о том, что this в С++  может быть нулем.
Хотел ровно ту же фичу применить для JSon объектов.

Обрати внимание на мое дополнение к тому посту (про свободную функцию) плз. Прежде чем плодить говнокод.

Вызов метода ведёт к проверке this на null (UB не будет, если класс не содержит виртуальных функций), если null — то это пустое значение, обрабатываемое отдельно. Почему же автор мудак?

Потому что this не может быть нулем. Вызов метода на нулевом this это ничто иное как все то же разыменование нулевого указателя (p->method()), что является оговоренным UB. Виртуальность здесь постольку поскольку (твоему компилятору все-таки придется слазить по нулевому указателю в таблицу виртуальных функций и обломаться).

valexey_u

  • Hero Member
  • *****
  • Сообщений: 3013
    • Просмотр профиля
Re: C++ инициализация ссылок
« Ответ #86 : Март 21, 2013, 09:19:15 pm »
Функция встроилась. Далее используется ref в качестве аргумента. Больше нигде ref не используется. Поэтому временный результат не сохраняется. А вы вот всё же вызовите вслед функцию из внешней среды, например WinAPI с этим самым ref. И он магическим образом обретёт себе хранилище.
Не понял, а для кого я printf вызывал? Это как раз и есть та самая фукнция из внешней среды. Могу еще раз вызвать - результат не изменится :-) C WinAPI (если бы он существовал на линуксе) было бы то же самое.
Y = λf.(λx.f (x x)) (λx.f (x x))

Berserker

  • Sr. Member
  • ****
  • Сообщений: 254
    • Просмотр профиля
Re: C++ инициализация ссылок
« Ответ #87 : Март 21, 2013, 09:23:28 pm »
Цитировать
Обрати внимание на мое дополнение к тому посту (про свободную функцию) плз. Прежде чем плодить говнокод.
Так я и пришёл к выводу, что null в качестве реального объекта безопаснее. Тем не менее, для класса без наследников, родителей и виртуальных функций обращения к VMT нет, как и самой VMT.

Цитировать
Не понял, а для кого я printf вызывал? Это как раз и есть та самая фукнция из внешней среды. Могу еще раз вызвать - результат не изменится :-) C WinAPI (если бы он существовал на линуксе) было бы то же самое.
Так добавьте ещё один вызов того же printf, но уже с ref * 2. Второй вызов и последующее использование ref не дадут его соптимизировать.

vlad

  • Hero Member
  • *****
  • Сообщений: 1391
    • Просмотр профиля
Re: C++ инициализация ссылок
« Ответ #88 : Март 21, 2013, 09:24:49 pm »
А проверка на &this == nullptr не спасла бы отца русской демократии в таком случае? :)

Есть надежда, что писатель лишний раз бы задумался - как может получиться такой объект у которого адрес (&) нулевой. И на стал бы пистаь такую херню.  Потому как в случае установки "this - это указатель" шансов задуматься чуть меньше (указатели естественно могут быть нулевыми). Хотя гарантий, конечно, никаких нет...

valexey_u

  • Hero Member
  • *****
  • Сообщений: 3013
    • Просмотр профиля
Re: C++ инициализация ссылок
« Ответ #89 : Март 21, 2013, 09:27:51 pm »
Все указатели остались во внутренних потрохах Lexems, наружу торчат только ссылки и человеческие методы (возможно swap надо переименовать во что-то более осмысленное). Не? Или lexems.current()/lexems.prev() вызывают нарекания лишними скобочками? Тогда ничем помочь не могу :)
Откровенно говоря, я вообще не вижу в данном случае тут делать prev/current тут, падон, членами. См. begin, end в современном stl'e например. Если прямо таки хочется инкапсуляции, то эти функции не члены могут быть френдами.

Тогда это все будет выглядеть как prev(lexems) и current(lexems), swap(lexems).
Y = λf.(λx.f (x x)) (λx.f (x x))