На главную Наши проекты:
Журнал   ·   Discuz!ML   ·   Wiki   ·   DRKB   ·   Помощь проекту
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS
msm.ru
Модераторы: Qraizer, Hsilgos
  
> Conditional and comma operators junction , в одном выражении в смысле.
    Есть такой код. Синтетический ради упрощения, однако пример из реальной жизни.
    ExpandedWrap disabled
      char *ptr;
      int   selector;
       
      void func(void) {}
       
      int main()
      {
        ptr = selector ? ptr + selector : (func(), 0);
      }
    Формально этот код легален с точки зрения обоих языков. За пруфами отсылать в Стандарты не буду, но если кому надо, могу привести цитаты. И тем не менее, этот код отвергается множеством GNUсных реализаций, с резюме, мол, невозможно привести int к char*, хотя конкретно тут 0 легко выступает в роли null pointer constant. Даже CLang зачастую недоволен, но последние ревизии, вроде, не возражают. Студии, к слову этом дефектом не страдают.
    Вопрос: действительно ли тут всё в порядке, и это GNUсный дефект, или же это дефект Стандарта?

    Добавлено
    P.S. Замечу, что если убрать operator,(), то проблем нет. По факту тут может быть нюанс в том, что 0 – это целочисленный литерал, тогда как результат operator,(), равный 0, это уже ни разу не литерал, хотя и по-прежнему prvalue.
      Qraizer, и всеж, пожалуйста, отошли к Стандарту по вот этой хрени:

      ExpandedWrap disabled
        (func(), 0)

      Что это такое? >:( Новая какая-то шляпа??
        Comma operator.
        Цитата 5.18 Comma operator
        1. The comma operator groups left-to-right.
           expression:
             assignment-expression
             expression , assignment-expression
        Обычно применяется в первом выражении инструкции for(), типа:
        ExpandedWrap disabled
          for (i = 0, j = 0; ...)
        За этими пределами встречается редко, однако очень любим средствами инструментирования кода. Вычисляется сначала левый операнд, затем применяется точка следования, результат вычислений отбрасывается, и в заключение вычисляется правый операнд, чьё значение и тип и определяет значение и тип результата comma operator-а. Инструментаторы так встраивают в код, подлежащий изучению в ран-тайм, свои точки наблюдения, не меняя его логики и поведения.

        Добавлено
        P.S. У нас так инструментируется код для сбора покрытия MC/DC. В качестве точек наблюдения выступают вызовы функций-регистраторов достижения необходимых условий для покрытия MC/DC.
          Т.е. сперва просто выполняется func(), а потом присваивается 0?
          Фигня какая-то. Или я что-то не догоняю??
            Вероятно, ты никогда не работал с инструментами наблюдения за поведением систем в динамике. Если тебе очень хочется подробностей, как подобный код вообще получился, то на. Звиняй, все персоны выдуманы, любые совпадения случайны. Реальный код под соглашением о конфиденциальности.
            Есть код типа
            ExpandedWrap disabled
              someValueType *res;
              /* ... */
              res = getSomeConditionResult(/* some params */) ? &someArray[someLongExpression].someField[anotherExpression].someAnotherField : NULL;
            Вполне невинный и правильный. Тут приходит время собирать покрытие. Берём RTRT, травим её на сий код. Получаем что-то вроде
            ExpandedWrap disabled
              res = (atl_cond(traceTag(someTagNumber), getSomeConditionResult(/* some params */))) ? (atl_DC(traceTag(someTagNumber), 1), &someArray[someLongExpression].someField[anotherExpression].someAnotherField) : (atl_DC(traceTag(someTagNumber), 0), NULL);
            Эта страшная строчка ещё ничего, ты бы видел if(), в которых логическое выражение из пяточка подвыражений разной степени приоритетности логических операций. Впрочем, а что вы хотели от алгоритмов автоматической генерации кода. Та и компилятору пофигу, ему лишь бы синтаксис и грамматика были соблюдены, а они соблюдены.
            Что тут происходит. В первом операнде ?: вызывается функция atl_cond() с первым параметром, однозначно (все someTagNumber в каждой точке наблюдения уникальны в пределах единицы трансляции; макрос traceTag() делает его уникальным в пределах всего проекта, комбинируя с уникальным кодом текущей единицы трансляции; это позволяет правильно обрабатывать даже статические функции и тем более inline-овые из .h-заголовков) указывающая на точку в исходнике, докуда дошёл поток исполнения, и вторым параметром – собственно результатом логического выражения, на основании значения которого код далее должен пойти по одной из двух веток. Выражение в точности исходное, как оно было в неинструментированном коде. Функция atl_cond() его никак не обрабатывает и берёт его лишь для того, чтобы вернуть как свой результат, не меняя т.с. поведения инструментированного кода по сравнению с неинструментированным. Остальные два параметра оператора ?: оформлены аналогично друг другу, но чуть иначе в виду небулевости их типов... точнее даже неопределённости их типов на момент сборки библиотек RTRT. Сначала вызывается функция atl_DC(), регистрирующая, что в только что зарегистрированной atl_cond() точке код пошёл по пути true или false, в зависимости от ветки ?:, затем её результат отбрасывается и выполняется собственно то, что в неинструментированном коде было изначально, т.б. либо второй, либо третий операнд соответственно. В результате система сбора покрытия детектирует фрагменты статистики, которые потом суммарно покажут DC-покрытие этого участка кода по итогам отработки всех тестовых сценариев.
            Заметь, сохранение исходного, неиструментированного, поведения кода строго важно для статистической значимости результатов сбора покрытия по инструментированному коду. Поэтому в наших "Инструкциях по разработке тестов" все тесты гоняются дважды: на инструментированном и неинструментированном коде, и при этом результаты обоих запусков должны совпадать до отдельных циферок и буковок. Пасс – значит пасс в обоих отчётах, фэйл – значит в обоих отчётах фэйл. И никак иначе. Любые несовпадения означают, что либо инструмент инструментирования кривой, и потому не смог оставить поведение кода неизменным, либо тест кривой, и потому имеет ненадёжные тестовые сценарии, либо код кривой, и потому имеет implementation defined или undefined behavior. В крайних случаях такие несоответствия должны быть объяснены, но только после индивидуального исследования и обязательно с аргументацией, почему конкретно тут это не ошибка.
            Беда в том, в этом проекте NULL – это не тот, который из stdlib.h. К сожалению, он определён самостоятельно как
            ExpandedWrap disabled
              #define NULL 0
            И всё бы ничего, т.к. литерал 0 в соответствие с обоими Стандартами может выступать как null pointer constant.
            Сообщение отредактировано: Qraizer -
              Цитата Qraizer @
              Вопрос: действительно ли тут всё в порядке, и это GNUсный дефект, или же это дефект Стандарта?

              Сколько вопросов сразу и вдобавок взаимоисключающих !
              Стандарт не может быть не правым, поскольку он начальник.
              Значит, ошибаются все остальные.
              ---
              Это, вероятно, дефект GNU.
              Починить исходник можно попробовать так:
              ExpandedWrap disabled
                 
                 ptr = selector ? ptr + selector : (func(), (char*)0);
              Сообщение отредактировано: ЫукпШ -
                ЫукпШ, это не вопрос. Вопрос в несоответствии поведения разных компиляторов, из-за чего непонятно, кто прав, кто виноват. Апелляция к Стандарту тут оправдана, но не факт, что и там нет очередного дефекта, иначе с чего бы компиляторам себя вести по-разному.
                  Цитата Qraizer @
                  Апелляция к Стандарту тут оправдана, но не факт, что и там нет очередного дефекта, иначе с чего бы компиляторам себя вести по-разному.

                  Тогда можно ответить таким образом:
                  - Если один компилятор работает по Стандарту, а другой - нет,
                  то прав тот, кто работает по Стандарту.
                  - Программисту от этого не легче.
                  Что толку от исходника, который вполне "правильный", но не везде компилируется ?
                  В этом случае исходник "правильный", а программист не прав.
                  ---
                  Поэтому исходник надо изменить так, чтобы он всё равно был "правильным"
                  и компилировался везде.
                  Тогда и программист перейдёт в разряд "правильных". :)
                  Сообщение отредактировано: ЫукпШ -
                    Цитата Qraizer @
                    Вопрос: действительно ли тут всё в порядке, и это GNUсный дефект, или же это дефект Стандарта?
                    Это слишком буквальное следование стандарту. (func(), 0) хоть и имеет всегда нулевое значение, тем не менее является не константой, а выражением. А согласно стандарту только константа 0 имеет одновременно и тип int и тип <что-то там>*. То есть по стандарту здесь должно выдаваться предупреждение.

                    А то, что MSный компилятор не ругается, так он во многих местах допускает вольности.

                    Так что лучше перед 0 явно указать тип, к которому надо его привести. Здесь можно старым C-шным способом (char*)0.
                    Сообщение отредактировано: amk -
                      Ну вот в этом-то и вопрос, amk. Смотри. В любом (ну, наверное, прям-таки все-все ревизии я не смотрел) Стандарте понятие константного выражения, помимо определения свойств операндов, определяется также посредством фразеологизма типа "вычисляется посредством" с указанием операций. По-разному, где-то приводятся "запрещённые" операции, где-то наоборот. Суть в том, что вызовы функций и кое-где, но не везде, comma operation "запрещённые" с точки зрения возможности рассматривать выражение как константное. Т.е. если 0 безусловно является целочисленным константным выражением, то (func(), 0) целиком уже вроде бы нет. Но.
                      В любом (см. дисклеймер выше) Стандарте присутствует лазейка. Например в C++14 это фраза "A conditional-expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine (1.9), would evaluate one of the following expressions:...". Поясняю: несмотря на то, что function invovation, отличной от constexpr, присутствует, для вычисления собственно результата comma operation это не требуется, следовательно это "запрещающее" правило не охватывает этот случай. Или C89: "A constant expression can be evaluated during translation rather than runtime, and accordingly may be used in any place that a constant may be." Вообще без комментариев. Но тут же чуть ниже comma и function-call упоминаются в списке запрещённых.
                      ИМХО там везде неоднозначность либо неполнота. Потому и нет однозначно правильного Стандартного виденья этой конструкции. Посему это скорее дефект Стандарта.

                      P.S. Ещё раз уточню, что вопрос не в том, насколько хорош/плох приведённый код, и как его исправить.
                      Сообщение отредактировано: Qraizer -
                        Собственно, тут проблема в том, что стандарт не оговаривает, можно ли в данном случае заведомо константное результирующее значение операции запятая считать эквивалентным константе 0, которую разрешается спокойно конвертить к указателю, или надо рассматривать её как результат выражения со неким значением конкретного типа (в данном случае int), которое конвертить в указатель в принципе можно, но это небезопасно и требует предупреждения (в Си).

                        Разработчики GCC посчитали, что в данном случае запятая выдаёт типизированное значение (дескать, после запятой мог бы стоять и не ноль).
                        MS решили, что раз значение можно определить из текста, и оно равно нулю, то его можно трактовать, как константу.

                        Тут не только в константности дело. 0 - единственная целая константа, которая используется в качестве указателя (нулевого). Строго говоря, константное выражение, вычисляемое в ноль, уже имеет тип, и не должно тихо преобразовываться в указатель. По тому же стандарту не нулевое значение конвертируется в нулевой указатель, а нулевая константа означает нулевой указатель.
                          Цитата amk @
                          Собственно, тут проблема в том, что стандарт не оговаривает, можно ли в данном случае заведомо константное результирующее значение операции запятая считать эквивалентным константе 0, которую разрешается спокойно конвертить к указателю
                          Вот-вот. Явно о , ничего не говорится, а если попытаться вывести это из других утверждений, получается неоднозначность.
                          Цитата C89
                          An integral constant expression with the value 0, or such an expression cast to type void * , is called a null pointer constant. If a null pointer constant is assigned to or compared for equality to a pointer, the constant is converted to a pointer of that type
                          Цитата C++14
                          A null pointer constant is an integer literal (2.14.2) with value zero or a prvalue of type std::nullptr_t. A null pointer constant can be converted to a pointer type;...
                          Затык именно в том, можно ли вон ту конструкцию считать "целочисленным константным выражением" или нет. С моей точки зрения получается так, что оно не попадает под описанные формальные признаки константности, но вполне падает под приведённое там же определение константности.
                          В общем, я-таки склоняюсь к Стандартному дефекту, и думаю, куда б об этом сообщить, пока C++20 не приняли.
                            Всё же, просто константное выражнение, вычисляемое в ноль, по моему мнению нельзя приводить к типу указателя. В смысле надо поступать так же, как в случае ненулевого константного выражения.

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

                            В принципе можно проверить, значение какого типа возвращено предыдущим (перед ',') выражением, и какое требуется в объемлющем выражении, но это требует анализа довольно большого контекста.
                            Поэтому я склоняюсь к тому, что в любом случае надо выдавать предупреждение - ситуация неоднозначна и может свидетельствовать об ошибке. Явное указание типа 0 дополнительно улучшает понимание текста.
                            Вообще, давно следовало ввести явное обозначение для нулевого указателя, не связанное с целочисленным нулём, оставив 0 лишь для совместимости.
                              Не вижу поводов для возражений, amk.
                              0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
                              0 пользователей:


                              Рейтинг@Mail.ru
                              [ Script execution time: 0,0415 ]   [ 16 queries used ]   [ Generated: 28.03.24, 12:37 GMT ]