LINUX.ORG.RU

Werror - контроль качества или занудство?

 ,


0

4

Все, кто занимается низкоуровневым жонглированием байтами в реалых промышленных условиях (с кучей поставщиков со всего мира, а не в уютненьком молодёжном стартапе где-нибудь в Саннивейл), понимают, что C’шка с нами надолго, лет на 20, если не больше. И поэтому возникает желание максимально использовать существующие технологии для обеспечения качества кода.

Например, Werror (и то, что к нему полагается в виде -Wall, -Wextra и проч.). Но возникают такие ситуации, как например с «целочисленным повышением» и последующим сравнением с разным знаком. Например:

const unsigned x = 12;
unsigned char y;
unsigned char z;
... // что-то кладём в y и z
if (x < (y*z))
{
   // тра-ля-ля
}

И y*z превращаются («брюки превращаются, превращаются брюки…») в элегантный int, и вылезает предупреждение о различной знаковости, как бы совершенно на ровном месте. Т.е. теперь, чтобы ублажить компилятор, надо дополнительно, например, явно кастануть x к int’у. Т.е., код уже на пределе читаемости (выше пример - это сильное упрощение возможной реальной ситуации), и тут мы ещё добавляем вовсе не интуитивный (int).

И возникает вопрос: а стоит ли овчинка (-Werror и ко.) выделки? Я сейчас, очевидно, не имею в виду код наивысшей критичности, а такой, который при случае можно просто неспеша поправить, в конце рабочего дня, с нулевыми последствиями для пользователей, окружающей среды и т.п.

P.S. кстати, поскольку в расте тоже есть беззнаковые типы, там тоже нечто подобное должно быть, или как?

UPD: в примере, в нагрузку к умножению надо ещё добавить сложение с ещё одним unsigned char.

★★★★★

Последнее исправление: seiken (всего исправлений: 1)
Ответ на: комментарий от anonymous

К сожалению нет. Еще больше запутался.

Я бы не делал выводы базируясь на warnings - скорее всего Вы имеете дело с false negatives. Есть куда как более надёжные способы понять что происходит (по крайней мере в плюсовом мире).

bugfixer ★★★★★
()
Ответ на: комментарий от wandrien

С практической да, но были архитектуры, где размеры char и short равны размеру int.

Они не были, а 16-битные char/short/int всё ещё есть и чипы по-прежнему штампуют.

gag ★★★★★
()
Ответ на: комментарий от Werenter

пфф, так сишники и сами себе хорошо набрасывают, в соседнем треде вон опять героически парсят строки, и вроде бы даже справились, но сомнения остались

zurg
()

P.S. кстати, поскольку в расте тоже есть беззнаковые типы, там тоже нечто подобное должно быть, или как?

Там такое не собирается. Неявные касты - это рак, который нужно убрать из каждого адекватного языка.

pub fn main() {
    let a = 1i32;
    let b = 1i64;
    let c = 1u32;
    
    let d = a + b;
    let e = a + c;
}
  Compiling playground v0.0.1 (/playground)
error[E0308]: mismatched types
 --> src/main.rs:7:17
  |
7 |     let d = a + b;
  |                 ^ expected `i32`, found `i64`

error[E0277]: cannot add `i64` to `i32`
 --> src/main.rs:7:15
  |
7 |     let d = a + b;
  |               ^ no implementation for `i32 + i64`
  |
  = help: the trait `Add<i64>` is not implemented for `i32`
  = help: the following other types implement trait `Add<Rhs>`:
            <i32 as Add>
            <i32 as Add<&i32>>
            <&'a i32 as Add<i32>>
            <&i32 as Add<&i32>>

error[E0308]: mismatched types
 --> src/main.rs:8:17
  |
8 |     let e = a + c;
  |                 ^ expected `i32`, found `u32`

error[E0277]: cannot add `u32` to `i32`
 --> src/main.rs:8:15
  |
8 |     let e = a + c;
  |               ^ no implementation for `i32 + u32`
  |
  = help: the trait `Add<u32>` is not implemented for `i32`
  = help: the following other types implement trait `Add<Rhs>`:
            <i32 as Add>
            <i32 as Add<&i32>>
            <&'a i32 as Add<i32>>
            <&i32 as Add<&i32>>

Some errors have detailed explanations: E0277, E0308.
For more information about an error, try `rustc --explain E0277`.
error: could not compile `playground` (bin "playground") due to 4 previous errors
anonymous-angler ★☆
()
Ответ на: комментарий от anonymous-angler

А как это разруливать - зависит от ситуации. Без каста, безопасно.

let d = i64::from(a) + b;
let e = i64::from(a) + i64::from(c);

С адекватным кастом

let d = a as i64 + b;
let e = a as i64 + c as i64;

С неадекватным кастом

let d = a + b as i32;
let e = a + c as i32;
anonymous-angler ★☆
()
Ответ на: комментарий от seiken

Safe стремится к тому что бы предоставлять одну единственную гарантию - гарантию отсутствия UB. Переполнение переменной - не UB. В расте, на сколько помню, определено что будет происходить при переполнении переменной - она «перевернётся».

anonymous-angler ★☆
()
Ответ на: комментарий от anonymous

Для завершения/продолжения темы integer promotions в этом треде. Может кому-то полезно будет, из книги «Effective C an introduction to professional C programming» Robert C. Seacord:

Prior to the first C Standard, compilers used one of two approaches to integer promotions: the unsigned preserving approach or the value preserving approach. In the unsigned preserving approach, the compiler promotes unsigned small types to unsigned int. In the value preserving approach, if all values of the original type can be represented as an int, the value of the original small type will be converted to int. Otherwise, it is converted to unsigned int. When developing the original version of the standard (C89), the C Standards committee decided on value preserving rules, because they produce incorrect results less often than the unsigned preserving approach. If necessary, you can override this behavior by using explicit type casts …

The result of promoting small unsigned types depends on the precision of the integer types, which is implementation-defined. For example, the x86 architecture has an 8-bit char type, a 16-bit short type, and a 32-bit int type. For implementations that target this architecture, values of both unsigned char and unsigned short are promoted to signed int because all the values that can be represented in these smaller types can be represented as a signed int. However, 16-bit architectures, such as Intel 8086/8088 and the IBM Series/1, have an 8-bit char type, a 16-bit short type, and a 16-bit int type. For implementations that target these architectures, values of type unsigned char are promoted to signed int, while values of type unsigned short are promoted to unsigned int. This is because all the values that can be represented as an 8-bit unsigned char type can be represented as a 16-bit signed int, but some values that can be represented as a 16-bit unsigned short cannot be represented as a 16-bit signed int.

Определение precision - is the number of bits used to represent values, excluding sign and padding bits.

anonymous
()
23 декабря 2023 г.

тут палка о двух концах. если у тебя продакшн и фиксированный тулчейн - можно бороться за чистоту нравов и использовать -Werror, наводя марафет. правда, от ошибок в коде это не избавит. но выглядеть будет чуть приятнее.

а если это опенсорц, который будет собираться на куче разных конфигураций, архитектур, библиотек и тулчейнов - это в принципе довольно бесполезено. потому что каждый новый компилятор считает своим долгом обнаружить «ошибки» там, где всё до него прекрасно работало.

Iron_Bug ★★★★★
()
Ответ на: комментарий от Iron_Bug

Любая программа поддерживает определенные версии компиляторов. Поддерживать абстрактный С это утопия, тем более когда новые стандарты ломают старый код. А те, кто собирают программу на неподдерживаемом компиляторе, должны быть готовы добавлять новые опции. К примеру ничего не мешает точечно отключать сработавшие предупреждения, не убирая Werror.

vbr ★★★★★
()
Последнее исправление: vbr (всего исправлений: 1)
Ответ на: комментарий от vbr

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

Iron_Bug ★★★★★
()

И y*z превращаются («брюки превращаются, превращаются брюки…») в элегантный int, и вылезает предупреждение о различной знаковости, как бы совершенно на ровном месте. Т.е. теперь, чтобы ублажить компилятор, надо дополнительно, например, явно кастануть x к int’у.

Это не «ублажение компилятора», а вполне реальная проблема в коде. Каст беззнакового целого к знаковому может привести к переполнению. В результате у тебя в лучшем случае вместо большого положительного числа получится большое отрицательное, что может привести к неочевидным багам в коде, если он не рассчитан на такие финты. Но это ещё полбеды: переполнение знакового целого по стандарту является UB, так что если код собирается без -fwrapv или -ftrapv, то баги могут быть намного «веселее».

Так что беззнаковые типы нужно использовать с большой осторожностью, и если без них обойтись нельзя, то внимательно обращаться с кастами.

annulen ★★★★★
()