Этот код выдает средние предупреждения в строках с return
:
// Checks if the symbol defines two-symbols Unicode sequence
bool doubleSymbol(const char c) {
static const char TWO_SYMBOLS_MASK = 0b110;
return (c >> 5) == TWO_SYMBOLS_MASK;
}
// Checks if the symbol defines three-symbols Unicode sequence
bool tripleSymbol(const char c) {
static const char THREE_SYMBOLS_MASK = 0b1110;
return (c >> 4) == THREE_SYMBOLS_MASK;
}
// Checks if the symbol defines four-symbols Unicode sequence
bool quadrupleSymbol(const char c) {
static const char FOUR_SYMBOLS_MASK = 0b11110;
return (c >> 3) == FOUR_SYMBOLS_MASK;
}
PVS говорит, что выражения всегда ложны (V547), но на самом деле это не так: char
может быть частью символа Unicode, который читается std::string
!
Вот Unicode представление символов:
1 byte - 0xxx'xxxx - 7 bits
2 bytes - 110x'xxxx 10xx'xxxx - 11 bits
3 bytes - 1110'xxxx 10xx'xxxx 10xx'xxxx - 16 bits
4 bytes - 1111'0xxx 10xx'xxxx 10xx'xxxx 10xx'xxxx - 21 bits
Следующий код подсчитывает количество символов в тексте Unicode:
size_t symbolCount = 0;
std::string s;
while (getline(std::cin, s)) {
for (size_t i = 0; i < s.size(); ++i) {
const char c = s[i];
++symbolCount;
if (doubleSymbol(c)) {
i += 1;
} else if (tripleSymbol(c)) {
i += 2;
} else if (quadrupleSymbol(c)) {
i += 3;
}
}
}
std::cout << symbolCount << "\n";
Для Hello!
введите выход 6
и для Привет, мир!
является 12
— это правильно!
Я не прав или PVS что-то не знает? 😉
Анализатор PVS-Studio знает, что существуют типы знаков со знаком и без знака. Использование подписанного или неподписанного зависит от ключей компиляции, и анализатор PVS-Studio учитывает эти ключи.
Я думаю, что этот код скомпилирован, когда char имеет тип char со знаком. Посмотрим, к чему это приведет.
Давайте рассмотрим только первый случай:
bool doubleSymbol(const char c) {
static const char TWO_SYMBOLS_MASK = 0b110;
return (c >> 5) == TWO_SYMBOLS_MASK;
}
Если значение переменной ‘c’ меньше или равно 01111111, условие всегда будет ложным, поскольку во время сдвига максимальное значение, которое вы можете получить, равно 011.
Это означает, что нас интересуют только случаи, когда старший бит в переменной ‘c’ равен 1. Поскольку эта переменная имеет тип char со знаком, то старший бит означает, что в переменной хранится отрицательное значение. Перед сдвигом знаковый символ становится подписанным int, а значение продолжает оставаться отрицательным.
Теперь давайте посмотрим, что стандарт говорит о сдвиге вправо отрицательных чисел:
Значение E1 >> E2 — это биты E2, сдвинутые вправо E1. Если E1 имеет тип без знака или E1 имеет тип со знаком и неотрицательное значение, значение результата является неотъемлемой частью отношения E1 / 2 ^ E2. Если E1 имеет тип со знаком и отрицательное значение, результирующее значение определяется реализацией.
Таким образом, сдвиг отрицательного числа влево определяется реализацией. Это означает, что старшие биты заполнены нулями или единицами. Оба будут правильными.
PVS-Studio считает, что старшие биты заполнены битами. Он имеет полное право так думать, потому что нужно выбирать любую реализацию. Таким образом, получается, что выражение ((c) >> 5) будет иметь отрицательное значение, если старший бит в переменной ‘c’ изначально равен 1. Отрицательное число не может быть равно TWO_SYMBOLS_MASK.
Оказывается, с точки зрения PVS-Studio условие всегда будет ложным, и оно правильно выдает предупреждение V547.
На практике компилятор может вести себя по-другому: старшие биты будут заполнены 0, и тогда все будет работать правильно.
В любом случае необходимо исправить код, так как он соответствует поведению компилятора, определяемому реализацией.
Код может быть исправлен следующим образом:
bool doubleSymbol(const unsigned char c) {
static const char TWO_SYMBOLS_MASK = 0b110;
return (c >> 5) == TWO_SYMBOLS_MASK;
}
Других решений пока нет …