Cython дает «ненужное» предупреждение - PullRequest
1 голос
/ 10 мая 2019

Следующий скрипт Cython приводит к предупреждению comparison between signed and unsigned integer expressions.

%%cython
# distutils: language = c++

from libcpp.vector cimport vector as cpp_vec
import numpy as np

cdef cpp_vec[long] a  = np.array([1,2,3], np.int)
cdef Py_ssize_t i

for i in range(a.size()):        # Using a.size() as parameter of range() causes the warning
    pass

Нужно ли это предупреждение?Если так, то почему?Кроме того, возможно ли заставить замолчать эти предупреждения о сравнении со знаком без знака?

Кроме того, почему только первый цикл for приводит к предупреждению?

%%cython
cdef:
    ssize_t i
    unsigned long m = 10
    unsigned int n = 10
    long o = 10

for i in range(m):
    pass
for i in range(n):
    pass
for i in range(o):
    pass

1 Ответ

1 голос
/ 11 мая 2019

Это предупреждение от вашего g ++ - компилятора, и к каждому предупреждению компилятора следует относиться серьезно.

Цикл for вашего кода Cython преобразуется в следующий / похожий код cpp:

 std::vector<long>::size_type __pyx_t_7;
 Py_ssize_t __pyx_t_8;
 __pyx_t_7 = __pyx_v_xxxx_a.size();
 for (__pyx_t_8 = 0; __pyx_t_8 < __pyx_t_7; __pyx_t_8+=1) {
     ....
 }

Проблема в том, что std::vector<long>::size_type - это 64-разрядное целое число без знака в 64-разрядной системе, а Py_ssize_t - 64-разрядное целое число со знаком.

C ++ использует неявное преобразование для встроенных типов, чтобы иметь возможность оценить __pyx_t_8 < __pyx_t_7, правила можно найти, например, в этом SO-post .

Существует несколько причин, по которым это предупреждение имеет смысл - правила довольно сложны, и опыт показывает, что программисты часто неправильно их обрабатывают. Например, -1<1U оценивается как false (см. live ), но

signed char a =-1;
unsigned char b = 1;
std::cout<<(a<b)<<"\n";

печатает 1, т.е. (a<b) оценивается как true (см. live ). Такие причуды могут легко привести к труднодоступным ошибкам.

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

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

1. Перейти с потоком:

Не проще ли сделать i a size_t вместо Py_ssize_t, т.е. cdef size_t i?

2. В ролях и проверке:

Вы можете явным образом привести и затем проверить, в порядке ли допущения, например,

...
cdef Py_ssize_t i

cdef Py_ssize_t n = <Py_ssize_t>(a.size())
if n<0 :
    raise ValueError("too big");

for i in range(n):
...

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

3. Только что произнес:

Кто-то может спросить: "Насколько вероятно, что вектор содержит более 2^63 записей?" и просто пропустите проверку:

...
for i in range(<Py_ssize_t>(a.size())):        
    print(i) 

и затем ... код не работает через 30 лет:)

4. Отключить предупреждение компилятора:

Также можно передать опцию -Wno-sign-compare компилятору через extra_compile_args в файле настроек или -c=-Wno-sign-compare в IPython, что отключит предупреждение во всем коде. Вероятно, безопаснее использовать решение «Just cast», которое отключает предупреждение только в этом месте, а не везде.


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

Например, ssize_t имеет 64 бита, а unsigned int имеет 32 бита, поэтому 32-битный бит без знака преобразуется в 64-битный ssize_t перед сравнением - переполнение не будет, поскольку положительные числа до 63 могут быть представлены `ssize_t'.

...