LINUX.ORG.RU

Разница в результатах работы программы для 32/64 бита

 , ,


0

1

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

Хорошо то, что это выявляет тестовый набор данных, т.е. легко воспроизводится.

Это не вычислительная задача, где могут появится ошибки округления.

Я вижу в первую очередь несколько причин: разный размер для int, разное выравнивание в структурах, арифметика (переполнения).

Есть ли общая стратегия выявления причин таких проблем?

PS местами там реальный говнокод

★★★★★

О, отличный пост. Мне кажется, что у кого-то где-то что-то не работает. Хоть бы код показал и тест-кейс.

DELIRIUM ☆☆☆☆☆
()

Статический анализ может найти такие ошибки. Не все и не всегда, но стоит попробовать, если отлаживать сложно. Предупреждения компилятора тоже могут указать на возможную причину.

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

Сам проект живет на github.com/ntop/nDPI

Я его несколько лет правлю для использования в netfilter/iptables

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

Вопрос с чего начать? Уж больно долго ходить отладчиком :(

Наверно есть смысл собрать без оптимизации ?

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

Это очень старый проект (openndpi) который успешно загнулся, а его исходики перешли по наследству ntop. Часть этих исходников была сгенерирована и очень плохо читаемая.

Они (ntop) его потихоньку исправляют, причесывают, развивают. А т.к. это фактически единственная доступная реализация DPI, то я вижу смысл поддерживать вариант для netfilter/iptables

vel ★★★★★
() автор топика

Пока подтвердилась худшая версия - влияение оптимизатора компилятора :(

vel ★★★★★
() автор топика

местами там реальный говнокод

ответ в этом

quester ★★
()

Я вижу в первую очередь несколько причин: разный размер для int

На x86-32 и x86-64 в системах GNU/Linux размеры int одинаковы.

Есть ли общая стратегия выявления причин таких проблем?

Советую попробовать что-то из -fsanitize опций, например -fsanitize=undefined. https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html

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

На x86 и x86-64 в системах GNU/Linux размеры int одинаковы.

имелось ввиду long int

int отличался на 16/32 битах

Советую попробовать что-то из -fsanitize опций, например -fsanitize=undefined

Спасибо. В мане даже ссылка на вики есть.

vel ★★★★★
() автор топика
Ответ на: комментарий от Zenom
int ndpi_comp_with_mask (void *addr, void *dest, u_int mask) {
  if( /* mask/8 == 0 || */ memcmp (addr, dest, mask / 8) == 0) {
    int n = mask / 8;
    int m = ((-1) << (8 - (mask % 8)));
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
third_party/src/ndpi_patricia.c:79:19: runtime error: left shift of negative value -1

    if(mask % 8 == 0 || (((u_char *)addr)[n] & m) == (((u_char *)dest)[n] & m))
      return (1);
  }
  return (0);
}

Этот код сравнивает 2 ip-адреса (v4 или v6) c учетом маски :(

До этого я уже сталкивался с проблемами в этой библиотеке. Замена безумного дефайна на обычную функцию избавила меня от глюков.

Я нашел пример в виде 1 пакета на котором при разной оптимизации появляются разные результаты. Так что ходить отладчиком будет не долго.

vel ★★★★★
() автор топика

int олного размера. Выравнивание не в курсе. long вот точно разного размера(вообще, смотри в LP64).

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

Просто замените этот код (ndpi_patricia.c) на код из py-radix. Оригинал у них один, зато код в py-radix гораздо чище.

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

int m = ((-1) << (8 - (mask % 8)));

u_int m = ((~0u) << (8 - (mask % 8)));

anonymous
()

Есть ли общая стратегия выявления причин таких проблем?

общая стратегия - писать код нормально, по-другому никак

clover
()

проблема как-то связана с плавающей запятой?

я просто знаю что есть офигенная разница в одном месте

ckotinko ☆☆☆
()
Ответ на: комментарий от clover

сейчас придёт ckotinko и скажет что должно работать норм

anonymous
()

местами там реальный говнокод

Первым делом избавится от него, дальше будет легче.

А вообще, изначально желательно писать, учитывая разную длину машинного слова, а потом тестить.

И кстати, даже только на 64 битных платформах при использовании разных компиляторов тоже могут возникнуть проблемы, если не задумываться о размерах целочисленных типов. Так, в gcc 64-бит sizeof(long)==8, а в MSVC 64-бит sizeof(long)==4.

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

С пор C89.

А можно ссылку на это место в стандарте си любой версии, начиная с си89? И да, если речь идёт о разной реализации знакового сдвига вправо на разных машинах, то это не совсем то.

Даже в яве, где в принципе нет беззнаковых типов, имеется знаковый сдвиг и специальный беззнаковый сдвиг вправо.

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

А можно ссылку на это место в стандарте си любой версии, начиная с си89?

Открой любой из них, главу про bitwise shift operators.

если речь идёт о разной реализации знакового сдвига вправо

Если ты не смотришь на сообщения, на которые отвечаешь (Разница в результатах работы программы для 32/64 бита (комментарий)), то обращаю внимание, что там речь идёт о левом сдвиге.

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

А можно ссылку на это место в стандарте си любой версии, начиная с си89?

Открой любой из них, главу про bitwise shift operators.

Спасибо. Посмотрел здесь http://www.open-std.org/JTC1/SC22/WG14/www/docs/C99RationaleV5.10.pdf :

6.5.7 Bitwise shift operators

The description of shift operators in K&R suggests that shifting by a long count should force 30 the left operand to be widened to long before being shifted. A more intuitive practice, endorsed by the C89 Committee, is that the type of the shift count has no bearing on the type of the result.

QUIET CHANGE IN C89

Shifting by a long count no longer coerces the shifted operand to long.

The C89 Committee affirmed the freedom in implementation granted by K&R in not requiring the signed right shift operation to sign extend, since such a requirement might slow down fast code and since the usefulness of sign extended shifts is marginal. (Shifting a negative two’s-complement integer arithmetically right one place is not the same as dividing by two!)

И говорится здесь именно о сдвиге вправо, и совсем не о том, что это в принципе неопределённое поведение, а о том, что компилятор при знаковом сдвиге вправо не обязан заполнять левые ячейки знаковым битом, а может делать это так, как быстрее получается. Т. е. если машина поддерживает знаковый сдвиг вправо (с заполнением левых битов не 0, а значением знака), то и компилятор это поддерживает, а иначе знаковый сдвиг реализуется так же, как беззнаковый. И касается это только сдвига вправо, т. к. сдвиги влево что со знаком, что без знака друг от друга не отличаются. О чём я и говорил.

если речь идёт о разной реализации знакового сдвига вправо

Если ты не смотришь на сообщения, на которые отвечаешь (Разница в результатах работы программы для 32/64 бита (комментарий)), то обращаю внимание, что там речь идёт о левом сдвиге.

Конечно же я читаю каменты, на которые отвечаю. Просто, как уже сказал, говорить о знаковом или беззнаковом сдвиге влево вообще бессмысленно, т. к. на такой сдвиг знак вообще никакого влияния не оказывает. Именно поэтому в яве и нет беззнакового сдвига влево.

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

Спасибо. Посмотрел здесь

Это не стандарт. И даже не его драфт.

говорить о знаковом или беззнаковом сдвиге влево вообще бессмысленно, т. к. на такой сдвиг знак вообще никакого влияния не оказывает.

С т.з. стандарта C или C++ оказывает.

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

Это не стандарт. И даже не его драфт.

Ещё раз спасибо. Просто открыл первую попавшуюся ссылку из соседнего треда.

говорить о знаковом или беззнаковом сдвиге влево вообще бессмысленно, т. к. на такой сдвиг знак вообще никакого влияния не оказывает.

С т.з. стандарта C или C++ оказывает.

Ага, нашёл.

http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf :

6.5.7 Bitwise shift operators

Syntax

1

shift-expression:
   additive-expression
   shift-expression << additive-expression
   shift-expression >> additive-expression

Constraints

2 Each of the operands shall have integer type.

Semantics

3 The integer promotions are performed on each of the operands. The type of the result is that of the promoted left operand. If the value of the right operand is negative or is greater than or equal to the width of the promoted left operand, the behavior is undefined.

91) Another way to approach pointer arithmetic is first to convert the pointer(s) to character pointer(s): In this scheme the integer expression added to or subtracted from the converted pointer is first multiplied by the size of the object originally pointed to, and the resulting pointer is converted back to the original type. For pointer subtraction, the result of the difference between the character pointers is similarly divided by the size of the object originally pointed to.

When viewed in this way, an implementation need only provide one extra byte (which may overlap another object in the program) just after the end of the object in order to satisfy the ‘‘one past the last element’’ requirements.

4 The result of E1 << E2 is E1 left-shifted E2 bit positions; vacated bits are filled with zeros. If E1 has an unsigned type, the value of the result is E1 × 2 ^ E2, reduced modulo one more than the maximum value representable in the result type. If E1 has a signed type and nonnegative value, and E1 × 2 ^ E2 is representable in the result type, then that is the resulting value; otherwise, the behavior is undefined.

5 The result of E1 >> E2 is E1 right-shifted E2 bit positions. If E1 has an unsigned type or if E1 has a signed type and a nonnegative value, the value of the result is the integral part of the quotient of E1 / 2E2. If E1 has a signed type and a negative value, the resulting value is implementation-defined.

Но блин, люди, вы же программисты! Не читайте же стандарты и другие документы бездумно! Включайте при чтении мозги!

В п. 6.5.7 говорится, что сдвиг влево, в случае, если сдвигаемое число положительное либо беззнаковое, следует интерпретировать как умножение на 2^на_что_сдвигаем. Если же число отрицательное, то в этих терминах результат будет действительно неопределённым. Так, если мы будем сдвигать влево -1 длиной в 1 байт, т. е. в двоичном виде 11111111b, то при сдвиге на 1 мы получим -2, при сдвиге на 2 — -4 и т. д. Но если мы начнём сдвигать число -86 (10101010b), то через раз будем получать то положительные, то отрицательные числа. Причём всё вышесказанное справедливо для дополненного до 2 кода. Если отрицательные числа представляются как-то иначе (а это возможно), то всё будет совсем по-другому. Но все эти неопределённости не определены до тех пор, пока мы пытаемся с помощью сдвигов реализовать умножение на степень 2. Если же мы рассматриваем сдвиг просто как сдвиг, а число — как набор двоичных бит, то всё становится очевидным, однозначным и определённым. И доказать это элементарно: приведите отрицательное число к типу unsigned и сдвиньте его, — по стандарту всё определено. Но что изменилось? Абсолютно ничего, кроме интерпретации числа, если записывать его в десятичном виде. Читайте стандарт вдумчиво!

aureliano15 ★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.