Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
||
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[13.59.82.167] |
|
Сообщ.
#1
,
|
|
|
Есть такой код. Синтетический ради упрощения, однако пример из реальной жизни.
char *ptr; int selector; void func(void) {} int main() { ptr = selector ? ptr + selector : (func(), 0); } Вопрос: действительно ли тут всё в порядке, и это GNUсный дефект, или же это дефект Стандарта? Добавлено P.S. Замечу, что если убрать operator,(), то проблем нет. По факту тут может быть нюанс в том, что 0 – это целочисленный литерал, тогда как результат operator,(), равный 0, это уже ни разу не литерал, хотя и по-прежнему prvalue. |
Сообщ.
#2
,
|
|
|
Qraizer, и всеж, пожалуйста, отошли к Стандарту по вот этой хрени:
(func(), 0) Что это такое? Новая какая-то шляпа?? |
Сообщ.
#3
,
|
|
|
Comma operator.
Цитата 5.18 Comma operator Обычно применяется в первом выражении инструкции for(), типа:1. The comma operator groups left-to-right. expression: assignment-expression expression , assignment-expression for (i = 0, j = 0; ...) Добавлено P.S. У нас так инструментируется код для сбора покрытия MC/DC. В качестве точек наблюдения выступают вызовы функций-регистраторов достижения необходимых условий для покрытия MC/DC. |
Сообщ.
#4
,
|
|
|
Т.е. сперва просто выполняется func(), а потом присваивается 0?
Фигня какая-то. Или я что-то не догоняю?? |
Сообщ.
#5
,
|
|
|
Вероятно, ты никогда не работал с инструментами наблюдения за поведением систем в динамике. Если тебе очень хочется подробностей, как подобный код вообще получился, то на. Звиняй, все персоны выдуманы, любые совпадения случайны. Реальный код под соглашением о конфиденциальности.
Есть код типа someValueType *res; /* ... */ res = getSomeConditionResult(/* some params */) ? &someArray[someLongExpression].someField[anotherExpression].someAnotherField : NULL; res = (atl_cond(traceTag(someTagNumber), getSomeConditionResult(/* some params */))) ? (atl_DC(traceTag(someTagNumber), 1), &someArray[someLongExpression].someField[anotherExpression].someAnotherField) : (atl_DC(traceTag(someTagNumber), 0), NULL); Что тут происходит. В первом операнде ?: вызывается функция atl_cond() с первым параметром, однозначно (все someTagNumber в каждой точке наблюдения уникальны в пределах единицы трансляции; макрос traceTag() делает его уникальным в пределах всего проекта, комбинируя с уникальным кодом текущей единицы трансляции; это позволяет правильно обрабатывать даже статические функции и тем более inline-овые из .h-заголовков) указывающая на точку в исходнике, докуда дошёл поток исполнения, и вторым параметром – собственно результатом логического выражения, на основании значения которого код далее должен пойти по одной из двух веток. Выражение в точности исходное, как оно было в неинструментированном коде. Функция atl_cond() его никак не обрабатывает и берёт его лишь для того, чтобы вернуть как свой результат, не меняя т.с. поведения инструментированного кода по сравнению с неинструментированным. Остальные два параметра оператора ?: оформлены аналогично друг другу, но чуть иначе в виду небулевости их типов... точнее даже неопределённости их типов на момент сборки библиотек RTRT. Сначала вызывается функция atl_DC(), регистрирующая, что в только что зарегистрированной atl_cond() точке код пошёл по пути true или false, в зависимости от ветки ?:, затем её результат отбрасывается и выполняется собственно то, что в неинструментированном коде было изначально, т.б. либо второй, либо третий операнд соответственно. В результате система сбора покрытия детектирует фрагменты статистики, которые потом суммарно покажут DC-покрытие этого участка кода по итогам отработки всех тестовых сценариев. Заметь, сохранение исходного, неиструментированного, поведения кода строго важно для статистической значимости результатов сбора покрытия по инструментированному коду. Поэтому в наших "Инструкциях по разработке тестов" все тесты гоняются дважды: на инструментированном и неинструментированном коде, и при этом результаты обоих запусков должны совпадать до отдельных циферок и буковок. Пасс – значит пасс в обоих отчётах, фэйл – значит в обоих отчётах фэйл. И никак иначе. Любые несовпадения означают, что либо инструмент инструментирования кривой, и потому не смог оставить поведение кода неизменным, либо тест кривой, и потому имеет ненадёжные тестовые сценарии, либо код кривой, и потому имеет implementation defined или undefined behavior. В крайних случаях такие несоответствия должны быть объяснены, но только после индивидуального исследования и обязательно с аргументацией, почему конкретно тут это не ошибка. Беда в том, в этом проекте NULL – это не тот, который из stdlib.h. К сожалению, он определён самостоятельно как #define NULL 0 |
Сообщ.
#6
,
|
|
|
Цитата Qraizer @ Вопрос: действительно ли тут всё в порядке, и это GNUсный дефект, или же это дефект Стандарта? Сколько вопросов сразу и вдобавок взаимоисключающих ! Стандарт не может быть не правым, поскольку он начальник. Значит, ошибаются все остальные. --- Это, вероятно, дефект GNU. Починить исходник можно попробовать так: ptr = selector ? ptr + selector : (func(), (char*)0); |
Сообщ.
#7
,
|
|
|
ЫукпШ, это не вопрос. Вопрос в несоответствии поведения разных компиляторов, из-за чего непонятно, кто прав, кто виноват. Апелляция к Стандарту тут оправдана, но не факт, что и там нет очередного дефекта, иначе с чего бы компиляторам себя вести по-разному.
|
Сообщ.
#8
,
|
|
|
Цитата Qraizer @ Апелляция к Стандарту тут оправдана, но не факт, что и там нет очередного дефекта, иначе с чего бы компиляторам себя вести по-разному. Тогда можно ответить таким образом: - Если один компилятор работает по Стандарту, а другой - нет, то прав тот, кто работает по Стандарту. - Программисту от этого не легче. Что толку от исходника, который вполне "правильный", но не везде компилируется ? В этом случае исходник "правильный", а программист не прав. --- Поэтому исходник надо изменить так, чтобы он всё равно был "правильным" и компилировался везде. Тогда и программист перейдёт в разряд "правильных". |
Сообщ.
#9
,
|
|
|
Цитата Qraizer @ Это слишком буквальное следование стандарту. (func(), 0) хоть и имеет всегда нулевое значение, тем не менее является не константой, а выражением. А согласно стандарту только константа 0 имеет одновременно и тип int и тип <что-то там>*. То есть по стандарту здесь должно выдаваться предупреждение.Вопрос: действительно ли тут всё в порядке, и это GNUсный дефект, или же это дефект Стандарта? А то, что MSный компилятор не ругается, так он во многих местах допускает вольности. Так что лучше перед 0 явно указать тип, к которому надо его привести. Здесь можно старым C-шным способом (char*)0. |
Сообщ.
#10
,
|
|
|
Ну вот в этом-то и вопрос, 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. Ещё раз уточню, что вопрос не в том, насколько хорош/плох приведённый код, и как его исправить. |
Сообщ.
#11
,
|
|
|
Собственно, тут проблема в том, что стандарт не оговаривает, можно ли в данном случае заведомо константное результирующее значение операции запятая считать эквивалентным константе 0, которую разрешается спокойно конвертить к указателю, или надо рассматривать её как результат выражения со неким значением конкретного типа (в данном случае int), которое конвертить в указатель в принципе можно, но это небезопасно и требует предупреждения (в Си).
Разработчики GCC посчитали, что в данном случае запятая выдаёт типизированное значение (дескать, после запятой мог бы стоять и не ноль). MS решили, что раз значение можно определить из текста, и оно равно нулю, то его можно трактовать, как константу. Тут не только в константности дело. 0 - единственная целая константа, которая используется в качестве указателя (нулевого). Строго говоря, константное выражение, вычисляемое в ноль, уже имеет тип, и не должно тихо преобразовываться в указатель. По тому же стандарту не нулевое значение конвертируется в нулевой указатель, а нулевая константа означает нулевой указатель. |
Сообщ.
#12
,
|
|
|
Цитата 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 не приняли. |
Сообщ.
#13
,
|
|
|
Всё же, просто константное выражнение, вычисляемое в ноль, по моему мнению нельзя приводить к типу указателя. В смысле надо поступать так же, как в случае ненулевого константного выражения.
Но в случае когда после последней запятой явно указан 0, можно подумать. Это может быть ситуация вроде приведённой, когда функция вызывается перед нулевой константой, обозначающей нулевой указатель. Но это может быть и ситуация при отладке, когда вместо целого возвращаемого значения функции подставляется нулевое значение (естественно тоже целое). В принципе можно проверить, значение какого типа возвращено предыдущим (перед ',') выражением, и какое требуется в объемлющем выражении, но это требует анализа довольно большого контекста. Поэтому я склоняюсь к тому, что в любом случае надо выдавать предупреждение - ситуация неоднозначна и может свидетельствовать об ошибке. Явное указание типа 0 дополнительно улучшает понимание текста. Вообще, давно следовало ввести явное обозначение для нулевого указателя, не связанное с целочисленным нулём, оставив 0 лишь для совместимости. |
Сообщ.
#14
,
|
|
|
Не вижу поводов для возражений, amk.
|