Pull to refresh

Comments 32

В CompCert нет ошибки там жопа ваще.
Только и пришло на ум — «как страшно жить». Если у человека 4 компилятора показали ошибку, то как верить, скажем, ПО кардиоводителя, или ПО системы жизнеобеспечения космического корабля, особенно, если на борту люди?

Впрочем, какая жизнь без риска? :)
Там не верят, а проверяют. Решение в многочисленных и разнообразных тестах, которые покрывают максимум ситуаций. Т.к. в некоторых случаях ошибка может произойти, но будет не значительна. Точно так же, как любое техническое устройство может дать аппаратный сбой.
Собственно поэтому один из вариантов — берут показатели 3х вычислений и сравнивают.
Согласен. Да и мне, честно говоря, не приходилось с таким количеством разнообразий от разных компиляторов на одном коде сталкиваться. Повезло, наверное. :)

Граничные условия — всегда область трактований и удивлений.
> Обычные арифметические преобразования подразумевают, что оба операнда к оператору "+" должны быть преобразованы из типа «signed char» к типу «signed int» перед выполнением операции сложения.

Не так.
Если, к примеру, сложить 2 signed char'a то никаких преобразований не будет.
В данном случае они есть только потому что выполняется согласование типов — меньший (signed char: x) кастуется к большему (signed int: 1).
Константа 1 это signed int просто потому что нет постфикса (u/l/ll) и она вмещается в int.
Порядок проверки вместимости — int, long int, long long int.
То есть числовая константа никогда не может быть short или char, собственно поэтому и кастуется, а не из-за каких-то странных мыслей в голове автора.
А если быть точнее, то не «кастуется», а «промоутиться». Отличия — см. в стандарте.
Увы, в терминологии никогда не был силён, больше по практика.
Глянул в стандарт, там используются термины — явное/неявное преобразования.
Собственно явное это и есть type cast expressions, а неявное — это то, что делает сам компилятор, без команд пользователя.
Вы удиветесь, но в стандарте чётко написано, что вся знаковая целочисленная арифметика выполняется с int-ами, если по размеру подходит, если не подходит, то с long int-ами. Если вы не заметили преобразования, или если оптимизатор его выкинул, то это ещё не означает, что его не было :)
Действительно удивлюсь.
Покажите пруф, что ли.
Additive operators: 6.5.6, item 4 — «If both operands have arithmetic type, the usual arithmetic conversions are performed on
them»
arithmetic type: 6.2.5, item 18: "Integer and floating types are collectively called arithmetic types"
Integer types: 6.2.5, item 17: «The type char, the signed and unsigned integer types, and the enumerated types are collectively called integer types»
usual arithmetic conversions: 6.3.1.8, item 11: "флоаты... If both operands have the same type, then no further conversion is needed. условие выполнилось, дальше читать не нужно"

В общем если по-русски, то char — это целочисленный тип, целочисленный тип это арифметический тип.
Арифметические типы при сложении следуют общем правилам кастинга бинарных операторов. Первое же правило для целых чисел — если типы одинаковые, то всё ок, оставляем так.
Где здесь принудительная конвертация я не увидел.
Особенно если учесть ремарки о том, что неявная конвертация прописывается только в соответствующих главах про выражения. (6.5.xxx)
Нет, это не так:

#include <stdio.h>
#include <typeinfo>

int main () {
  char x = 1;
  int i = 1;
  
  printf("type:%s\n", typeid(x+x).name());
  printf("type:%s\n", typeid(x).name());
  printf("type:%s\n", typeid(i).name());
  return 0;
}

Запустите этот код у себя, компилятор вам скажет, что он думает о типах. В Стандарте это прописано в правилах о integral promotions
Действительно, упустил строку «Otherwise, the integer promotions are performed on both operands».
Мне вот когда-то понравился коммент в файле limits.h компилятора LLVM прямо перед определением CHAR_MAX и прочих констант:

/* Many system headers try to «help us out» by defining these. No really, we know how big each datatype is. */
Ужас какой.

Какие-то совершенно детские ошибки в компиляторах, которые и современные, и распространённые.
на платформах, где char — знаковый тип

Наличие знака скорее зависит не от платформы, а от опций компилятора (опций по умолчанию и опций, задаваемых вручную). В частности, есть повсеместная практика при разработке мультиархитектурных программ и библиотек прописывать для clang или gcc -funsigned-char.

Так вот, если использовать -funsigned-char, то в GCC 4.4/4.5/4.6/4.7 и Clang месячной давности всё работает правильно с любыми оптимизациями (1 1 1… 1 1 0).
А при опции -fsigned-char результат разный в зависимости от опций оптимизации. А обязан быть одинаковым.

Это баг.
++x — undefined behaviour, с чего бы результат должн быть одинаовый?
Ох, да. В Стандарте прописано, что только для беззнаковых целых гарантируется отсутствие реакции на переполнение (результат обязан усекаться).

Для знаковой арифметики — да, undefined behavior.
Запутали вы меня. Как выглядит обоснование, что ++x — это именно undefined behavior, а не implementation-defined behavior?
Это не undefined behavior, а Implementation-defined:
C99, 6.3.1.3:3
«Otherwise, the new type is signed and the value cannot be represented in it; either the
result is implementation-defined or an implementation-defined signal is raised.»
Да, именно. Этот «undefined behavior» — только для таких экзотических платформ, у которых (INT_MAX+1) вызывает реакцию, аналогичную делению на ноль.

Все остальные должны четко прописывать, что именно должно получаться в результате. У GCC же результат «плавает».

В багзилле этот баг живёт уже 4 (!) года.
Да, правильно, implementation defined. Но undefined behavior — это тоже вариант решения проблемы implementation defined. И gcc приняло такую позицию, видимо.
int foo (signed char x) {
  signed char y = x;
  //x++;  // #1
  x=x+1;  // #2
  return x > y;
}

Строки #1 и #2 должны быть эквивалентны для компилятора. Однако, они приводят к разным результатам у GCC.

Это баг.
Вот здесь баг, согласен. Так как инкремент и сравнение разделены точкой следования. А значит x должен был принять какое-то значение, которое в соотвествии с его типом не может быть больше SCHAR_MAX.
Совсем не должны они быть эквивалентны.
А вот если поменяете строку #2 на:
x = x + (signed char)1;
тогда можно будет об этой эквивалентности говорить.
Почему? Откуда это следует?
потому что в случае
signed char x = 'a';
x = x + 1;

в правой части сложение не двух signed char, а signed char и int, а это значит, что первый операнд неявно расширяется до int. При x++; такого не происходит.
Ещё раз повторю вопрос: откуда вы это взяли?

Т.к. (как я это вижу в Стандарте) при x++ происходит следующее:
1. вычисляется x + 1:
1.1. «x» расширяется до int.
1.2. вычисляется результат (int(x) + 1)
2. Полученный результат (типа int) присваивается переменной «x»:
2.1. Так как int «шире», чем «signed char», то происходит усечение до типа «signed char»
2.2. Усечённый результат заносится в «x»

Всё. Этот порядок следует из эквивалентности выражений x++ и x=x+1. Где в Стандарте упоминается обратное?
char+char=int, это из стандарта следует и это так. Так что указанное Вами приведение типа не влияет.
Если бы речь шла о embedded, то у вас даже не возникло бы такого вопроса.
Sign up to leave a comment.

Articles