Lapack: Разрешить возможную установку библиотеки index-64 вместе со стандартной библиотекой index-32?

Созданный на 1 нояб. 2020  ·  41Комментарии  ·  Источник: Reference-LAPACK/lapack

В настоящее время в debian есть (некоторые) отдельные заголовки для blas64 и cblas64 (но не из эталонной реализации).

Я не уверен, верны они или нет, что касается эталонного API index64 (они из библиотеки blis).

Можно ли добавить в cmake параметр, например BUILD_INDEX64 , который по умолчанию равен OFF , но если он был включен, он создаст библиотеки index-64?
Если я сделаю пиар для такого варианта, будет ли это развлечено как возможность?

Некоторые вещи, которые я имел в виду, чтобы позволить этому сосуществовать со стандартной установкой - назовите библиотеки libblas64.so, libcblas64.so, liblapack64.so, liblapacke64.so , таким образом не будет конфликта между именами библиотек (хотя, конечно, вы не можете связать одновременно с libblas и libblas64).
Кроме того, библиотеку нужно будет скомпилировать дважды: один раз для index32 и один раз для index64 (но это совершенно нормальный сценарий, а не нарушение сделки).
Единственный конфликт, о котором я не могу придумать хорошего разрешения, - это имена файлов заголовков.
Если вы следуете стилю debians, было бы целесообразно также называть заголовки c, заканчивающиеся на 64.
(Я поддерживаю это для Gentoo и хотел бы, чтобы экосистема была очень близка к debian, чтобы у нас были минимальные проблемы для разработчиков, переключающихся между системами)

Я открыт для любых предложений, прежде чем я сделаю пиар: heart:

Спасибо,
Аиша

Build System Enhancement

Все 41 Комментарий

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

Определенно, это звучит как хороший план.

Боже мой @langou
ты такой быстрый: heart:

Для полноты картины я записываю то, что нам еще предстоит сделать:

  • Выясните, как назвать заголовки, чтобы 32-битный API мог сосуществовать с 64-битным API.
  • Исправьте операторы printf / fprintf, чтобы они использовали правильный квалификатор для печати.

Любые предложения по решению первого вопроса приветствуются, у меня, к сожалению, нет "чистого" решения.

Несколько вопросов, которые помогут мне справиться с именованием файлов.

  • Кажется, есть тонна повторяющихся определений между cblas_f77.h и cblas_test.h . Нам это действительно нужно?
  • Нужно ли устанавливать cblas_test.h ? Учитывая его имя (и файлы, в которых он используется), я предполагаю, что он будет использоваться только на этапе тестирования. Может не стоит устанавливать этот файл на общесистемном уровне?

Привет @ epsilon-0,

Некоторые вещи, которые я имел в виду, чтобы позволить этому сосуществовать со стандартной установкой - назовите библиотеки libblas64.so, libcblas64.so, liblapack64.so, liblapacke64.so, таким образом, нет конфликта между именами библиотек ( хотя, конечно, вы не можете связываться одновременно с libblas и libblas64).

Возможно, вы ищете PR # 218. Автор этого PR - Бьорн Эссер из проекта Fedora.

Привет @ epsilon-0. Решил ли # 462 эту проблему?

@weslleyspereira нет, это еще не все.
Необходимо еще переименовать / обработать заголовок.
Я занят в ближайшие несколько недель, поэтому не собираюсь делать это в ближайшее время.
Основной план

  • файлы заголовков должны называться cblas.h и cblas64.h , как и другие заголовки.

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

  • файлы cmake должны быть установлены в lapack64 или cblas64 и т. д.

Да я вижу. Спасибо за быстрое наблюдение!

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

/usr/lib/libopenblas.so
/usr/lib/libopenblas64.so
/usr/lib/libblas.so
/usr/lib/libcblas.so
/usr/lib/libblas64.so
/usr/lib/libcblas64.so
/usr/include/openblas/cblas.h
/usr/include/openblas64/cblas.h
/usr/include/netlib/cblas.h
/usr/include/netlib64/cblas.h
/usr/include/cblas.h -> netlib/cblas.h (for compatibility, having the default)

(и так далее)

Мы не рассматриваем переключение среды выполнения, как бинарные дистрибутивы, поэтому ничего страшного, если каждый cblas.h (и lapacke.h) специфичен для соответствующей библиотеки, например, с дополнительными именами для libopenblas. Выбор времени сборки происходит через

BLAS_INCLUDES=-I/prefix/include/netlib64
BLAS_LIBS=-lblas64
CBLAS_LIBS=-lcblas64

(и т. д.) Это то, что должны сообщать файлы .pc, и это намного проще, чем передавать другое имя файла заголовка. В этом они пока непоследовательны, но я это исправляю. Похоже, что до сих пор люди просто взломали это в своих дистрибутивах, если вообще возились со всеми ссылочными библиотеками.

Однако у меня есть один вопрос по поводу этих заголовков.

Я взламываю сборку cmake, чтобы каждый компонент собирался отдельно, и пробую другое исправление (см. Https://github.com/Reference-LAPACK/lapack/pull/556). Я получаю библиотеки libblas.so и libblas64.so, настроенные, я настраиваю каталоги заголовков ... но установленные cblas.h и lapacke.h идентичны для 32- и 64-разрядной версии индексации. Это расходится с openblas: здесь у меня есть существенная разница, которую я не вижу для сборок netlib:

diff -ruN /data/pkg/include/openblas/openblas_config.h /data/pkg/include/openblas64/openblas_config.h
--- /data/pkg/include/openblas/openblas_config.h    2021-06-03 19:03:53.000000000 +0200
+++ /data/pkg/include/openblas64/openblas_config.h  2021-06-03 19:13:36.000000000 +0200
@@ -44,6 +44,7 @@
 #define OPENBLAS_DLOCAL_BUFFER_SIZE 32768
 #define OPENBLAS_CLOCAL_BUFFER_SIZE 16384
 #define OPENBLAS_ZLOCAL_BUFFER_SIZE 12288
+#define OPENBLAS_USE64BITINT 
 #define OPENBLAS_GEMM_MULTITHREAD_THRESHOLD 4
 #define OPENBLAS_VERSION " OpenBLAS 0.3.15 "
 /*This is only for "make install" target.*/

Для справочных библиотек все заголовки из 32- и 64-разрядных сборок индексов идентичны, и, по-видимому, пользователи должны вводить
-DWeirdNEC в своих флагах (30 лет назад это могло быть смешно) для cblas.h и -DLAPACK_ILP64 -DHAVE_LAPACK_CONFIG_H . Поскольку люди используют оптимизированные библиотеки BLAS в производстве, стандартом де-факто является не раскрытие этого пользователям. Они возвращаются к ссылке, IMHO, и заголовки, установленные из сборки ILP64, не должны требовать фанковых флагов, чтобы избежать сбоя вашего приложения при связывании с 64-битной библиотекой.

Согласны ли мы с тем, что это правильное решение - изменять заголовки во время сборки для определения правильных целых чисел?

Кстати, в установленных файлах конфигурации cblas также отсутствуют какие-либо ссылки на необходимые определения, поэтому они, как кажется, не работают для 64-битных сборок индекса. Но на самом деле я думаю, что их вообще не нужно устанавливать. Они являются избыточными для файлов .pc и, возможно, затрудняют убеждение зависимых пакетов, использующих cmake, принять выбор упаковщика через BLAS_LIBS etal.

PS: В Intel MKL необходимо установить центральный переключатель -DMKL_ILP64 . Я представляю себе тривиальную настройку
include/intel-mkl64/cblas.h с

#ifndef MKL_ILP64
#define MKL_ILP64
#endif
#include <mkl_cblas.h>

чтобы соответствовать общей схеме. Я мог бы также поместить определение в BLAS_INCLUDES, то же самое для странных определений netlib. Что лучше? Хотим ли мы делать это как Intel или как OpenBLAS?

Согласны ли мы с тем, что это правильное решение - изменять заголовки во время сборки для определения правильных целых чисел?

да. Я согласен с этим и предпочитаю решение, которое не воспроизводит весь заголовок. Думаю, чище.

Кстати, в установленных файлах конфигурации cblas также отсутствуют какие-либо ссылки на необходимые определения, поэтому они, как кажется, не работают для 64-битных сборок индекса.

Верно. Я только что установил 64-битные библиотеки (BUILD_INDEX64 = ON) и не увидел ничего, говорящего мне использовать WeirdNEC , LAPACK_ILP64 или HAVE_LAPACK_CONFIG_H . Спасибо, что заметили это!

да. Я согласен с этим и предпочитаю решение, которое не воспроизводит весь заголовок. Думаю, чище.

Для меня это неоднозначно. Какое решение более чистое? То, что я готовлю сейчас, такое:

#if defined(WeirdNEC) || @HAVE_ILP64@
   #define CBLAS_INDEX long
   #ifndef WeirdNEC
   #define WeirdNEC
   #endif
#else
   #define CBLAS_INDEX int
#endif

CMakeFile должен заменить HAVE_ILP на 1 или 0, получившийся заголовок устанавливается для текущей сборки.

(Между прочим: long не работает в Windows. Это долго там ... или int64_t на всех платформах с stdint.)

Верно. Я только что установил 64-битные библиотеки (BUILD_INDEX64 = ON) и не увидел ничего, говорящего мне использовать WeirdNEC , LAPACK_ILP64 или HAVE_LAPACK_CONFIG_H . Спасибо, что заметили это!

Я представляю будущее, в котором ты

cc -I/foo/include/netlib64 -o bar bar.c -L/foo/lib -lcblas64

И все обрабатывается в foo / include / netlib64 / cblas.h, в противном случае - в foo / include / netlib / cblas.h (возможно, связанном с foo / include / cblas.h).

У меня есть подозрение, что это _не_ то, что вы имели в виду, но я хочу убедить, что так лучше ;-)

Вы можете попытаться не дублировать заголовок, поместив заголовок 'the' в /foo/include/cblas.h и включив его /foo/include/netlib64/cblas.h только путем определения WeirdNEC, но это означает, что 64 Битовые и 32-битные пакеты используют общий файл заголовка, который беспорядочно упаковывается. Лучше, если каждый будет помещать свой файл в отдельные места / имена. Имя должно оставаться cblas.h, потому что вы не хотите заменять #include <cblas.h> строками.

Изменить: Кроме того, наличие cblas.h include ../cblas.h само по себе беспорядочно. Также мы определяем директорию установки заголовка _one_ для cmake. По умолчанию это / foo / include, а не / foo / netlib64 / include. Я не собираюсь менять это значение по умолчанию. Упаковщики должны указать подкаталог следующим образом (BSD make в pkgsrc):

.if !empty(LAPACK_COMPONENT:M*64)
.  if empty(MACHINE_ARCH:M*64)
PKG_FAIL_REASON+=       "${LAPACK_COMPONENT} incompatible with non-64-bit platform"
.  endif
HEADERDIR=netlib64
.else
HEADERDIR=netlib
.endif

# Note: We patch the build to install both static and
# shared libraries.
CMAKE_ARGS=     -DBUILD_DEPRECATED=ON \
                -DBUILD_SHARED_LIBS=ON \
                -DBUILD_STATIC_LIBS=ON \
                -DCMAKE_INSTALL_INCLUDEDIR=${PREFIX}/include/${HEADERDIR} \
                ${LAPACK_COMPONENT_CMAKE_ARGS}

Прекрасным аспектом доставки / установки 32-битного cblas.h с этой модификацией в обычное место является то, что исходная механика все еще работает. Только 64-битный вариант будет применять WeirdNEC. Вы можете решить установить только 64-битную версию в префикс и оставить другие части экосистемы нетронутыми.

Да ладно ... CBLAS / cmake / cblas-config-install.cmake.in, кажется, забывает -DCMAKE_INSTALL_INCLUDEDIR, не так ли?

# Report lapacke header search locations.
set(CBLAS_INCLUDE_DIRS ${_CBLAS_PREFIX}/include)

(Комментарий сверху сахар.)

У меня такое чувство, что сборка CMake намного менее зрелая, как можно было бы подумать. Серьезен ли проект по поводу того, что это будет первичная сборка, или это всего лишь привлечение машины? Я действительно испытываю желание исправить Makefile старого стиля, меньше суеты вокруг. Но теперь я потратил так много времени на исправление материала CMake, что я все равно ненавижу. Так что я бы хотел покончить с этим.

Я должен сдаться сейчас ... Мне удалось переместить cblas.h в cblas.h.in, как указано выше, и добавить

configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cblas.h.in cblas.h @ONLY)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cblas_f77.h.in cblas_f77.h @ONLY)

в CBLAS / include / CMakeLists.txt, также определив @HAVE_ILP64@ равным 1 или 0 в файле CMakeLists.txt верхнего уровня. Но я не могу понять, как заставить установочный материал, который находится в CMakeLists.txt более высокого уровня, установить сгенерированные заголовки или странную копию того же из ${LAPACK_BINARY_DIR}/include (правда? A скопировать в исходное дерево?)

Что должен делать макрос append_subdir_files? Кажется, добавляет копию префикса к путям заголовка. У меня недостаточно или слишком много путей к исходным файлам заголовков. Я просто хочу установить файлы заголовков от ЗДЕСЬ к ЗДЕСЬ, черт возьми.

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

да. Я согласен с этим и предпочитаю решение, которое не воспроизводит весь заголовок. Думаю, чище.

Для меня это неоднозначно. Какое решение более чистое? То, что я готовлю сейчас, такое:

#if defined(WeirdNEC) || @HAVE_ILP64@
   #define CBLAS_INDEX long
   #ifndef WeirdNEC
   #define WeirdNEC
   #endif
#else
   #define CBLAS_INDEX int
#endif

CMakeFile должен заменить HAVE_ILP на 1 или 0, получившийся заголовок устанавливается для текущей сборки.

(Между прочим: long не работает в Windows. Это долго там ... или int64_t на всех платформах с stdint.)

Верно. Я только что установил 64-битные библиотеки (BUILD_INDEX64 = ON) и не увидел ничего, говорящего мне использовать WeirdNEC , LAPACK_ILP64 или HAVE_LAPACK_CONFIG_H . Спасибо, что заметили это!

Я представляю будущее, в котором ты

cc -I/foo/include/netlib64 -o bar bar.c -L/foo/lib -lcblas64

И все обрабатывается в foo / include / netlib64 / cblas.h, в противном случае - в foo / include / netlib / cblas.h (возможно, связанном с foo / include / cblas.h).

У меня есть подозрение, что это _не_ то, что вы имели в виду, но я хочу убедить, что так лучше ;-)

Простите, позвольте мне объяснить. Сначала мне понравилась идея сохранить исходный заголовок cblas.h и создать include/netlib64/cblas.h и include/netlib/cblas.h с чем-то вроде

#if defined(WeirdNEC)
   #define WeirdNEC
#endif
#include <cblas.h>

Вы можете попытаться не дублировать заголовок, поместив заголовок 'the' в /foo/include/cblas.h и включив его /foo/include/netlib64/cblas.h только путем определения WeirdNEC, но это означает, что 64 Битовые и 32-битные пакеты используют общий файл заголовка, который беспорядочно упаковывается. Лучше, если каждый будет помещать свой файл в отдельные места / имена. Имя должно оставаться cblas.h, потому что вы не хотите заменять #include <cblas.h> строками.

Изменить: Кроме того, наличие cblas.h include ../cblas.h само по себе беспорядочно. Также мы определяем директорию установки заголовка _one_ для cmake.

но да, нам пришлось бы использовать include/netlib64 и include в качестве подключаемых директорий, если один заголовок включает другой.

По умолчанию это / foo / include, а не / foo / netlib64 / include. Я не собираюсь менять это значение по умолчанию. Упаковщики должны указать подкаталог следующим образом (BSD make в pkgsrc):

.if !empty(LAPACK_COMPONENT:M*64)
.  if empty(MACHINE_ARCH:M*64)
PKG_FAIL_REASON+=       "${LAPACK_COMPONENT} incompatible with non-64-bit platform"
.  endif
HEADERDIR=netlib64
.else
HEADERDIR=netlib
.endif

# Note: We patch the build to install both static and
# shared libraries.
CMAKE_ARGS=     -DBUILD_DEPRECATED=ON \
                -DBUILD_SHARED_LIBS=ON \
                -DBUILD_STATIC_LIBS=ON \
                -DCMAKE_INSTALL_INCLUDEDIR=${PREFIX}/include/${HEADERDIR} \
                ${LAPACK_COMPONENT_CMAKE_ARGS}

Мне это кажется хорошим. Таким образом, вы могли бы добавить только альтернативу для сборки LAPACK без необходимости _ угадывать_ флаги компилятора. Но нынешний способ тоже подойдет.

(Между прочим: long не работает в Windows. Это долго там ... или int64_t на всех платформах с stdint.)

Хорошо знать. BLAS ++ и LAPACK ++ используют int64_t вместо long long.

@weslleyspereira Итак, вам сначала понравилась эта идея:

#if defined(WeirdNEC)
   #define WeirdNEC
#endif
#include "../cblas.h"

с /prefix/include/cblas.h и /prefix/include/netlib64/cblas.h, причем последний определяет местонахождение первого? Но теперь вы согласны с тем, что это более надежное решение для установки заголовка, который выглядит так для 64-битной сборки?

#if defined(WeirdNEC) || @HAVE_ILP64@
   #define CBLAS_INDEX long
   #ifndef WeirdNEC
   #define WeirdNEC
   #endif
#else
   #define CBLAS_INDEX int
#endif

(long vs. int64 - другое дело, но я полностью за это изменение, как и BLAS ++)

Черт возьми, я даже не уверен, можно ли с уверенностью предположить, что `#include" ... / cblas.h "найдет только другой заголовок с отступом. Стандарт C, кажется, говорит, что порядок поиска определяется реализацией, не обязательно относительно текущего заголовка. Моя основная проблема как упаковщика заключается в том, что мне нужен отдельный пакет для этого общего заголовка или 64-битный пакет зависит от 32-битного только для этого. Это было бы отстой.

Я действительно хотел бы пойти дальше с таким изменением для pkgsrc, чтобы внести изменения в исходный код позже. Мы могли бы обсудить новый символ для принудительного использования 32-битных индексов или 64-битных индексов явно с любым из заголовков ( -DNETLIB_INDEX_BITS=64 ?), Просто по умолчанию для того, с чем была построена библиотека.

Могу ли я договориться о нашем предполагаемом решении именно так?

lib/libcblas64.so
include/optional_subdir64/cblas.h

а также

lib/libcblas.so
include/optional_subdir/cblas.h

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

Затем я мог бы вставить это перед предстоящим выпуском pkgsrc (приближается крайний срок), и мы можем дополнительно обсудить детали этой реализации, чтобы я мог отказаться от исправлений после слияния чего-то здесь с новым выпуском LAPACK. С этим изменением простая сборка Makefile также нуждается в исправлении, но мне это еще не нужно для _my_ патчей, когда я просто использую сборку CMake.

(Просто нужно как-то сдержать мой характер, когда я пытаюсь превзойти эту странную сборку CMake в отправку, где он перетасовывает копии заголовков по каталогам сборки, а затем не может найти их для установки. Или решить, какие сломанные файлы .cmake могут нам пригодиться, может просто сбросить их из установки ... мы получили pkg-config!)

Что-нибудь? Должен признать, что я не вижу особых шансов для другого решения на практике, поскольку это пример, установленный openblas, основной реализацией, которую мы используем. Я могу представить, как убедить Intel иметь подкаталог для заголовков индексов 64/32-бит, обернув их mkl_cblas.h и mkl_lapacke.h. В противном случае я создаю простой пакет, который просто предоставляет их.

include/mkl-blas/cblas.h
include/mkl-blas64/cblas.h

В настоящее время я добавил в pkgsrc оборудование для создания сборок с забавной строкой -DWeirdNEC -DHAVE_LAPACK_CONFIG_H -DLAPACK_ILP64 , при этом как cblas, так и cblas64 устанавливают одинаковые заголовки. Так и могло остаться, но я все же думаю, что имеет смысл настроить заголовок в соответствии с ABI сборки.

@weslleyspereira Итак, вам сначала понравилась эта идея:

#if defined(WeirdNEC)
   #define WeirdNEC
#endif
#include "../cblas.h"

с /prefix/include/cblas.h и /prefix/include/netlib64/cblas.h, причем последний определяет местонахождение первого? Но теперь вы согласны с тем, что это более надежное решение для установки заголовка, который выглядит так для 64-битной сборки?

#if defined(WeirdNEC) || @HAVE_ILP64@
   #define CBLAS_INDEX long
   #ifndef WeirdNEC
   #define WeirdNEC
   #endif
#else
   #define CBLAS_INDEX int
#endif

Да это оно. Я согласен с вашим решением о наличии подпапок для 32- и 64-битных заголовков. Я обсуждал это с @langou , и он также был убежден, что это будет хорошим решением.

(long vs. int64 - другое дело, но я полностью за это изменение, как и BLAS ++)

Верно. Это нужно решить в другом выпуске.

Я действительно хотел бы пойти дальше с таким изменением для pkgsrc, чтобы внести изменения в исходный код позже. Мы могли бы обсудить новый символ для принудительного использования 32-битных индексов или 64-битных индексов явно с любым из заголовков ( -DNETLIB_INDEX_BITS=64 ?), Просто по умолчанию для того, с чем была построена библиотека.

Могу ли я договориться о нашем предполагаемом решении именно так?

lib/libcblas64.so
include/optional_subdir64/cblas.h

а также

lib/libcblas.so
include/optional_subdir/cblas.h

да. Я думаю, вы можете пойти дальше и предложить PR в будущем, спасибо! Я лично считаю, что новый символ вроде NETLIB_INDEX_BITS имеет смысл. Я бы просто убедился, что значение по умолчанию остается 32, и что -DWeirdNEC подразумевает -DNETLIB_INDEX_BITS=64 .

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

Звучит неплохо.

Затем я мог бы вставить это перед предстоящим выпуском pkgsrc (приближается крайний срок), и мы можем дополнительно обсудить детали этой реализации, чтобы я мог отказаться от исправлений после слияния чего-то здесь с новым выпуском LAPACK. С этим изменением простая сборка Makefile также нуждается в исправлении, но мне это еще не нужно для _my_ патчей, когда я просто использую сборку CMake.

Ok! У нас, вероятно, будет релиз LAPACK во втором семестре 2021 года. И да, Makefile должен быть соответствующим образом скорректирован, и я готов помочь с этим.

Это в некоторой степени связано. Мы не должны забывать, что заголовки для netlib CBLAS предоставляются не только netlib ... NumPy всегда использует свой собственный заголовок:

https://github.com/numpy/numpy/blob/main/numpy/core/src/common/npy_cblas.h

И в этом заголовке он устанавливает CBLAS_INDEX=size_t , отличный от целочисленного типа, используемого при указании индексов. Он используется исключительно для возвращаемых значений некоторых функций:

$ grep CBLAS_INDEX ./numpy/core/src/common/npy_cblas_base.h                                                                                                                                  
CBLAS_INDEX BLASNAME(cblas_isamax)(const BLASINT N, const float  *X, const BLASINT incX);
CBLAS_INDEX BLASNAME(cblas_idamax)(const BLASINT N, const double *X, const BLASINT incX);
CBLAS_INDEX BLASNAME(cblas_icamax)(const BLASINT N, const void   *X, const BLASINT incX);
CBLAS_INDEX BLASNAME(cblas_izamax)(const BLASINT N, const void   *X, const BLASINT incX);

Различия:

$ grep cblas_isamax ./numpy/core/src/common/npy_cblas_base.h  /data/pkg/include/cblas.h                                                                                                      
./numpy/core/src/common/npy_cblas_base.h:CBLAS_INDEX BLASNAME(cblas_isamax)(const BLASINT N, const float  *X, const BLASINT incX);
/data/pkg/include/cblas.h:CBLAS_INDEX cblas_isamax(const CBLAS_INDEX N, const float  *X, const CBLAS_INDEX incX);

Интересно, может ли это вызвать проблемы. Для Netlib существует только один тип индекса, в то время как другие реализации используют другой тип возвращаемого значения для индексных функций. OpenBLAS подает пример. Они говорят, что isamax возвращает unsigned size_t, но оболочка C фактически вызывает функцию Fortran, которая возвращает целое число со знаком (Edit: подпрограмма, которая записывает 32- или 64-битное целое число со знаком в переданную ссылку на 64-битную переменную без знака на 64 битовые системы).

Есть ли мнение по этому поводу у эталонной реализации? Я _guess_, нет никаких серьезных проблем, поскольку значение size_t всегда сможет удерживать любой неотрицательный возврат от isamax (). Но пахнет ненадежно. (Изменить: вы можете построить с 64-битными индексами в 32-битной системе, где size_t - 32 бита, верно? Тогда у вас было переполнение. Вдобавок к непростости преобразования size_t * в int * .)

Поскольку оптимизированные реализации, похоже, выбрали size_t, должна ли ссылка принять этот факт и следовать?

И насколько действительно опасно связывать numpy со справочным cblas?

OpenBLAS подает пример. (...)
Поскольку оптимизированные реализации, похоже, выбрали size_t, должна ли ссылка принять этот факт и следовать?

Я, конечно, не могу говорить о numpy (или mkl и т. Д. В этом отношении), но я бы не стал утверждать, что OpenBLAS является нормативным в любой форме, меньше всего относительно того, что (я считаю) обычно рассматривается как __the__ эталонная реализация .. .

Конечно. Просто OpenBLAS или MKL - это то, что люди используют на практике, и оба, похоже, остановились на

#define CBLAS_INDEX size_t  /* this may vary between platforms */
#ifdef MKL_ILP64
#define MKL_INT MKL_INT64
#else
#define MKL_INT int
#endif
CBLAS_INDEX cblas_isamax(const MKL_INT N, const float  *X, const MKL_INT incX);

или аналогично

#ifdef OPENBLAS_USE64BITINT
typedef BLASLONG blasint;
#else
typedef int blasint;
#endif
#define CBLAS_INDEX size_t
CBLAS_INDEX cblas_isamax(OPENBLAS_CONST blasint n, OPENBLAS_CONST float  *x, OPENBLAS_CONST blasint incx);

по сравнению со ссылкой

#ifdef WeirdNEC
   #define CBLAS_INDEX long
#else
   #define CBLAS_INDEX int
#endif
CBLAS_INDEX cblas_isamax(const CBLAS_INDEX N, const float  *X, const CBLAS_INDEX incX);

Как так получилось, что они расходятся с приведенной здесь ссылкой? Было ли сообщение об этом? Также… Я вижу, что MKL и OpenBLAS определяют множество функций, которые даже не являются частью справочного CBLAS:

CBLAS_INDEX cblas_isamin(const MKL_INT N, const float  *X, const MKL_INT incX);
CBLAS_INDEX cblas_idamin(const MKL_INT N, const double *X, const MKL_INT incX);
CBLAS_INDEX cblas_icamin(const MKL_INT N, const void   *X, const MKL_INT incX);
CBLAS_INDEX cblas_izamin(const MKL_INT N, const void   *X, const MKL_INT incX);

CBLAS_INDEX cblas_isamin(OPENBLAS_CONST blasint n, OPENBLAS_CONST float  *x, OPENBLAS_CONST blasint incx);
CBLAS_INDEX cblas_idamin(OPENBLAS_CONST blasint n, OPENBLAS_CONST double *x, OPENBLAS_CONST blasint incx);
CBLAS_INDEX cblas_icamin(OPENBLAS_CONST blasint n, OPENBLAS_CONST void  *x, OPENBLAS_CONST blasint incx);
CBLAS_INDEX cblas_izamin(OPENBLAS_CONST blasint n, OPENBLAS_CONST void *x, OPENBLAS_CONST blasint incx);

CBLAS_INDEX cblas_ismax(OPENBLAS_CONST blasint n, OPENBLAS_CONST float  *x, OPENBLAS_CONST blasint incx);
CBLAS_INDEX cblas_idmax(OPENBLAS_CONST blasint n, OPENBLAS_CONST double *x, OPENBLAS_CONST blasint incx);
CBLAS_INDEX cblas_icmax(OPENBLAS_CONST blasint n, OPENBLAS_CONST void  *x, OPENBLAS_CONST blasint incx);
CBLAS_INDEX cblas_izmax(OPENBLAS_CONST blasint n, OPENBLAS_CONST void *x, OPENBLAS_CONST blasint incx);

CBLAS_INDEX cblas_ismin(OPENBLAS_CONST blasint n, OPENBLAS_CONST float  *x, OPENBLAS_CONST blasint incx);
CBLAS_INDEX cblas_idmin(OPENBLAS_CONST blasint n, OPENBLAS_CONST double *x, OPENBLAS_CONST blasint incx);
CBLAS_INDEX cblas_icmin(OPENBLAS_CONST blasint n, OPENBLAS_CONST void  *x, OPENBLAS_CONST blasint incx);
CBLAS_INDEX cblas_izmin(OPENBLAS_CONST blasint n, OPENBLAS_CONST void *x, OPENBLAS_CONST blasint incx);

Итак, расширение стандарта - это одно, но size_t vs int кажется серьезной проблемой в 64-битных системах. Это должно быть решено каким-то образом. Мне кажется, что способ Netlib разумен: тот же тип, что и тот, который используется для индексов. Поскольку все в конце концов называют такие процедуры на Фортране

c     isamaxsub.f
c
c     The program is a fortran wrapper for isamax.
c     Witten by Keita Teranishi.  2/11/1998
c
      subroutine isamaxsub(n,x,incx,iamax)
c
      external isamax
      integer  isamax,iamax
      integer n,incx
      real x(*)
c
      iamax=isamax(n,x,incx)
      return
      end

… Передать адрес size_t для iamax, это кажется неправильным. Я не нашел другой реализации, кроме этой, в источниках OpenBLAS. Они просто глупо меняют внешний тип, или я упускаю из виду что-то очень простое? Кто-нибудь действительно использует эти функции?

Привет всем, эталонный BLAS, эталонный CBLAS, эталонный LAPACK. Двумя основными направлениями этих проектов являются (1) числовые алгоритмы и (2) определение общих интерфейсов, эталонная реализация и набор тестов для этого. Я думаю, что каждый, кто участвует в этих проектах, с удовольствием смотрит и учится на других проектах (OpenBLAS, MKL и т. Д.) О разработке программного обеспечения, передовых методах развертывания программного обеспечения и т. Д. Нам есть чему поучиться на этих проектах. (И мы также многому научились из других проектов числовой линейной алгебры!) В любом случае: справочные BLAS, CBLAS, LAPACK могут использовать некоторые улучшения в его упаковке CMake, интерфейсах, и если OpenBLAS (например) имеет лучший процесс, это хорошо подходит для мы, что ж, я полностью поддерживаю переход к этой модели.

Чтобы добавить некоторого контекста, CBLAS был создан комитетом (Технический форум по подпрограммам базовой линейной алгебры), который работал с 1996 по 2000 год над пересмотром BLAS, как часть этого они определили интерфейс C для BLAS. Видеть:
http://www.netlib.org/blas/blast-forum/
В частности см .:
http://www.netlib.org/blas/blast-forum/cinterface.pdf
Я считаю, что CBLAS, предлагаемый LAPACK, является реализацией интерфейса, определенного Техническим форумом по подпрограммам базовой линейной алгебры 25 лет назад.

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

Спасибо за указатель. Таким образом, соответствующая часть в этой спецификации выглядит как B.2.2, в которой говорится, что BLAS_INDEX обычно равно size_t , но также может быть выбрано идентичным (подписанному) целочисленному типу Fortran, используемому для индексация. Все зависит от реализации.

Таким образом, похоже, что популярные оптимизированные реализации выбрали size_t а ссылка на Netlib выбрала то же целое число, которое используется для Fortran. Я вижу копии cblas.h повсюду в различных проектах, которые используют библиотеку (например, numpy, отправка заголовка для внешней библиотеки), с этой строкой

#define CBLAS_INDEX size_t  /* this may vary between platforms */

В https://github.com/LuaDist/gsl/blob/master/cblas/gsl_cblas.h это сопровождается

/* This is a copy of the CBLAS standard header.
 * We carry this around so we do not have to
 * break our model for flexible BLAS functionality.
 */

Похоже, это зародилось в эталонной реализации, но с тех пор изменилось? Глядя на 41779680d1f233928b67f5f66c0b239aecb42774… я вижу, что переключатель CBLAS_INDEX с WeirdNEC был там до 64-битной сборки. Вау, это недавний коммит. Теперь я вижу, что size_t был в referenc cblas.h до 2015 года, 83fc0b48afd1f9a6d6f8dddb16e69ed7ed0e7242 изменил его и представил определение WeirdNEC. Я не предполагал, что это так недавно! Сильно сбивает с толку.

Я также вижу, что более ранняя версия cblas.h передала вызов fortran int , теперь CBLAS_INDEX . Теперь это кажется правильным, с последовательным использованием CBLAS_INDEX как целочисленного типа и переключателем для 32 или 64 бит в части Fortran.

Но может ли быть так, что в оптимизированных библиотеках, унаследовавших старую версию cblas.h с size_t но синхронизирующих источники с текущим кодом CBLAS из справочника, есть хорошая ошибка? Разве они не делают что-то подобное для 32-битного случая в 64-битной системе?

#include <stdio.h>
#include <stdlib.h>


void ia_max(int a, void *b)
{
    int *ib = (int*)b;
    *ib = a*2;
}


int main(int argc, char **argv)
{
    int i = atoi(argv[1]);
    size_t maxi;
    ia_max(i, &maxi);
    printf("in %d out %ld\n", i, (long)maxi);
    return 0;
}

Это приводит к

$ gcc -O -o t t.c
$ ./t 42
in 42 out 140724603453524

Инициализация значения size_t до нуля помогает, но, вероятно, только в случае с прямым порядком байтов. Никто из-за этого не попадает в беду? Я должен что-то упустить.

Заключить:

  1. Ссылка CBLAS сначала имела возвращаемое значение size_t .
  2. Однако он использовал int в фактическом вызове Fortran.
  3. Нисходящий поток (оптимизированный BLAS, пользователи CBLAS) работают со старой версией заголовка.
  4. Ссылка CBLAS представляет взлом WeirdNEC для конкретной системы, заменяя size_t на int или long (соответствует стороне Fortran ?!)
  5. 64-битный интерфейс Reference CBLAS строится поверх этого, используя везде CBLAS_INDEX для целого числа Fortran по умолчанию.
  6. Нисходящие потоки сделали свое дело с поддержкой 64-битной версии, но отделили ее от CBLAS_INDEX , который всегда равен size_t.
  7. Нисходящие потоки наследуют оболочки CBLAS, которые используют CBLAS_INDEX для вызова Fortran, ожидающего целое число по умолчанию.

В результате это звучит как чудесная поломка. Заголовки и код расходились. Почему никто еще не заметил проблем? Или я пропустил ту часть, где на самом деле не используется эталонный код оболочки CBLAS для isamax и друзей?

OpenBLAS, по крайней мере, не использует код оболочки CBLAS из Reference-LAPACK (и никогда не использовал, исходный код есть, но не создается)

@ martin-frbg Полезно знать. Можете ли вы указать путь кода для, скажем, x86-64, который показывает, как size_t передается фактическим вычислениям для cblas_isamax ()? Я нашел некоторую конкретную реализацию ядра, но не уверен в общем случае.

Было бы хорошо знать, что на самом деле никто не передает (size_t*) интерфейсу Fortran.

Конечно, нехорошо, что проекты просто предполагают

size_t cblas_isamax(…)

когда фактическая библиотека может предлагать в качестве возвращаемого значения int или long (или int64_t). Большую часть времени может работать со значениями в 64-битных регистрах, но это неприятно. Можем ли мы исправить это в реализациях? Люди не обращали внимания на пример Netlib за последние 5 лет о последовательном использовании CBLAS_INDEX .

соответствующий код находится в интерфейсе OpenBLAS /, например, interface / imax.c компилируется в cblas_isamax (), когда определен CBLAS, а код Fortran не участвует в его графе вызовов.

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

Я не нахожу фактического использования cblas_isamax() и друзей в NumPy (и SciPy), так что это может быть только теоретической проблемой. Тем не менее это должно быть исправлено. Так:

  1. Другие следуют примеру Netlib с использованием int32_t / int64_t (при этом давайте будем явным ;-) BLAS_INDEX как для возвращаемых размеров, так и для аргументов индекса.
  2. Netlib прогибается и возвращается к size_t для тех же возвратов, что и другие.

Это отдельный вопрос для обсуждения? Тем не менее, он позволяет выбрать 32- или 64-битную библиотеку.

PS: Я все еще не уверен, являются ли перечисления в API хорошей идеей (как фактический тип данных для аргументов функции и членов структуры), поскольку есть параметры компилятора, чтобы изменить то, какое целое число используется под ними. На практике это не так актуально, но, тем не менее, меня беспокоит.

Чем больше я думаю об этом, тем больше склоняюсь к варианту 2: у нас в API очень долгое время был size_t. Затем Netlib изменил этот size_t на int или long. Независимо от того, что лучше соответствует коду Fortran или может быть более последовательным, API size_t был установлен, и ссылка на Netlib нарушила это.

Стоит ли мне открывать PR об изменении вещей для

size_t cblas_isamax(const CBLAS_INDEX N, const float  *X, const CBLAS_INDEX incX);
size_t cblas_idamax(const CBLAS_INDEX N, const double *X, const CBLAS_INDEX incX);
size_t cblas_icamax(const CBLAS_INDEX N, const void   *X, const CBLAS_INDEX incX);
size_t cblas_izamax(const CBLAS_INDEX N, const void   *X, const CBLAS_INDEX incX);

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

В https://github.com/numpy/numpy/issues/19243 мы теперь в основном подошли к следующему: «К черту Netlib, size_t работает для всех остальных».

Есть три причины использовать size_t :

  1. Все стандартные библиотечные функции C и C ++ принимают и возвращают это значение, например, void* malloc(size_t) , size_t strlen() или std::size_t std::vector<T>::size() (C ++). Использование size_t позволяет избежать усечения значений и преобразований со знаком / без знака.
  2. size_t часто используется для выражения величин, которые не могут быть отрицательными, например размерности матрицы.
  3. Стандарты C и C ++ гарантируют, что вы можете хранить размер любого массива в size_t и что вы можете индексировать все элементы с помощью size_t , cf. cppference.com: size_t .

Изменить: вы можете построить с 64-битными индексами в 32-битной системе, где size_t составляет 32 бита, верно? Тогда у вас переполнение.

Нет, потому что 32-разрядная система может иметь более 4 ГБ виртуальной памяти (Linux поддерживает это), но один 32-разрядный процесс никогда не может получить доступ к более чем 4 ГБ. То есть верхние 32-битные из 64-битных индексов никогда не используются.

_ Ограничение памяти 32-битным процессом, работающим в 64-битной ОС Linux _

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

Но я чувствую себя обязанным придираться к вашим аргументам ;-)

1. All of the C and C++ standard library functions accept and return this value

Когда я исследовал это, я наткнулся на признание того, что использование беззнакового типа для индексов контейнера C ++ и, возможно, даже возвращаемого типа метода size () было исторической ошибкой, так как вы быстро заканчиваете тем, что смешиваете числа со знаком и без знака в каким-то образом. Текущее состояние Netlib будет согласовано с самим собой, всегда используя подписанные типы для размера и индексов, но, конечно, несовместимо с malloc() , которое требует, чтобы размер без знака действительно мог адресовать всю память, которая вписывается в 32 бита (или 64 бита теоретически).

Я сам задаюсь вопросом об этом в коде, который я написал, где я в конечном итоге передаю индекс как смещение для вызова функции. Индекс без знака, смещение со знаком. Помимо компиляторов (MSVC), которые сбивают с толку -unsigned_value , это означало бы, что мне всегда приходилось беспокоиться о возможном переполнении при преобразовании.

Но в любом случае, если речь идет просто о вычислении размеров памяти для передачи malloc () и его друзьям, size_t - это естественная вещь, и она уже была в CBLAS.

О возможных проблемах с текущим состоянием кода, несоответствие с поставленным cblas.h в сборках:

Нет, потому что 32-разрядная система может иметь более 4 ГБ виртуальной памяти (Linux поддерживает это), но один 32-разрядный процесс никогда не может получить доступ к более чем 4 ГБ. То есть верхние 32-битные из 64-битных индексов никогда не используются.

Правильно, size_t остается 32-битным. Когда вы (как бы глупо это ни звучало) построили cblas_isamax() чтобы вернуть 64-битное целое число, после взлома сборки, чтобы использовать не long , но int64_t , конечно, что будет неужели бывают в таком использовании?

size_t cblas_isamax(); // really int64_t cblas_isamax()!
size_t value = cblas_isamax(…);

Соглашение о вызовах x86 может помещать 64-битное значение в EAX и EDX. Или он может работать с возвратом указателя и некоторым буфером. Но что бы сделали другие архитектуры? Таким образом, вы можете не получить коррупцию, но наверняка неправильное значение. В лучшем случае игнорируются старшие 32 бита.

А теперь представьте 32-битную систему с прямым порядком байтов (некоторая форма ARM) ... уверены, что вы даже получите желаемую половину возвращаемого значения?

Конечно, вы не сможете работать с неразреженными данными, которым нужны 64-битные индексы в 32-битной программе. Но просто возможность вызвать несовпадающий вызов функции, который _at_least_ дает неправильные результаты, кажется нездоровой.

Я провел небольшое тестирование ... в x86 Linux ( gcc -m32 в системе x86-64) вы просто отбрасываете верхние 32 бита.

Более интересный случай… 64-битный size_t:

size_t cblas_isamax(); // really int32_t cblas_isamax()!
size_t value = cblas_isamax(…);

Опять же, на x86-64 особая связь между 64-битным RAX и 32-битным EAX делает вещи довольно четко определенными, чтобы также просто незаметно обнулять верхние 32 бита после выполнения 32-битной операции с общим регистром. Но есть удовольствие иметь немного странное определение функции:

$ cat ret32.c 
#include <stdint.h>

int32_t ret64(int64_t a)
{
    a += 1LL<<32;
    return a;
}
$ gcc -m64  -g -c -o ret32.o ret32.c 
$ LANG=C objdump -S ret32.o 
[…]
   8:   48 89 7d f8             mov    %rdi,-0x8(%rbp)
    a += 1LL<<32;
   c:   48 b8 00 00 00 00 01    movabs $0x100000000,%rax
  13:   00 00 00 
  16:   48 01 45 f8             add    %rax,-0x8(%rbp)
    return a;
  1a:   48 8b 45 f8             mov    -0x8(%rbp),%rax

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

$ cat call.c 
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

INDEX ret64(int64_t);

int main(int argc, char **argv)
{
    if(argc < 2)
        return 1;
    int64_t a = (int64_t)strtoll(argv[1], NULL, 10);
    INDEX  s = ret64(a);
    printf("%lld\n", (long long)s);
    return 0;
}
$ gcc -m64 -g -DINDEX=int32_t -c -o call32_64.o call.c
$ gcc -m64 -g -DINDEX=size_t -c -o call64_64.o call.c
$ ./call32_64 1
1
$ ./call64_64 1
4294967297

Веселье. 32-битное возвращаемое значение, которое дает больше, чем возможно в 32-битном. Вот что может случиться (в принципе), если текущее состояние Netlib CBLAS связано с кодом, который ожидает size_t. Я предполагаю, что на практике верхние 32 бита RAX будут равны нулю в реальном коде. Но кто знает ... компилятор ожидает, что вызывающий объект не будет использовать более 32 младших бита на любой платформе ... с таким же успехом может хранить там мусор.

Итак ... согласны ли мы вернуть Netlib к size_t в качестве возвращаемого значения?

Спасибо за все эти ценные комментарии!

Я немного обсудил эту тему с @langou. Основываясь на обсуждении здесь, мое предложение:

В отдельный пиар:

  1. Мы вернемся к cblas.h котором используются два целочисленных определения, скажем, CBLAS_INDEX и CBLAS_INT. Это то, что происходит в MKL (CBLAS_INDEX и MKL_INT) и OpenBLAS (CBLAS_INDEX и blasint). CBLAS_INDEX будет использоваться только при возврате i*amax . При этом мы восстанавливаем ABI, совместимый с другими BLAS.
  2. Кроме того, мы выбираем значение CBLAS_INDEX по умолчанию, равное size_t и собираем мнения сообщества.

Я думаю, что это согласованная (или, может быть, та же самая) идея, лежащая в основе недавних обсуждений в этой ветке.
Как отметил @drhpc ,
https://github.com/Reference-LAPACK/lapack/commit/83fc0b48afd1f9a6d6f8dddb16e69ed7ed0e7242 изменил значение CBLAS_INDEX по умолчанию и
https://github.com/Reference-LAPACK/lapack/commit/41779680d1f233928b67f5f66c0b239aecb42774 изменил использование CBLAS_INDEX.

Просто чтобы усилить:

  • OpenBLAS, MKL, GNU Scientific Library и Numpy по умолчанию используют size_t .
  • Интерфейс C для BLAS (https://www.netlib.org/blas/blast-forum/cinterface.pdf) указывает, что обычно CBLAS_INDEX = size_t .

Ты согласен? Если вы это сделаете, я могу открыть PR. Или, может быть, @drhpc захочет это сделать.

Я согласен. И, пожалуйста, просто продолжайте пиар.

@ mgates3 упомянул мне обсуждение в Slate Google Group:
https://groups.google.com/a/icl.utk.edu/g/slate-user/c/f5y6gt0aoLs/m/oQyyhikwCgAJ
Речь идет не о том, чем должен быть "CBLAS_INDEX", а о том, каким должен быть "CBLAS_INT". Должен ли CBLAS_INT быть size_t, целым числом со знаком и т. Д.? Я думаю, что участники высказывают хорошие соображения, поэтому я пропускаю их.

Пожалуйста, смотрите # 588.

Правильно, size_t остается 32-битным. Когда вы (как бы глупо это ни звучало) построили cblas_isamax() для возврата 64-битного целого числа, после взлома сборки не используйте long , но int64_t , конечно, что будет неужели бывают в таком использовании?

size_t cblas_isamax(); // really int64_t cblas_isamax()!
size_t value = cblas_isamax(…);

Соглашение о вызовах x86 может помещать 64-битное значение в EAX и EDX. Или он может работать с возвратом указателя и некоторым буфером. Но что бы сделали другие архитектуры? Таким образом, вы можете не получить коррупцию, но наверняка неправильное значение. В лучшем случае игнорируются старшие 32 бита.

А теперь представьте 32-битную систему с прямым порядком байтов (некоторая форма ARM) ... уверены, что вы даже получите желаемую половину возвращаемого значения?

Игра окончена. На 32-битных процессорах Arm четыре 32-битных значения могут передаваться и возвращаться в регистрах, 64-битные значения занимают два последовательных регистра, см. Раздел 6.1.1.1 в _Стандарт вызова процедур для архитектуры Arm_ . Вместо записи в один регистр вызываемый будет загромождать два регистра своими 64-битными целыми числами; это очевидно проблема. Как только у вызывающей стороны заканчиваются регистры для параметров, используется стек. Выравнивание стека составляет 32 бита, но вместо чтения или записи 32 бита вызываемый объект записывает 64 бита; Опять же, игра окончена, и эта проблема (несоответствие размеров чтения / записи стека) в какой-то момент должна вызвать проблемы во всех архитектурах набора команд.

Я сам задаюсь вопросом об этом в коде, который я написал, где я в конечном итоге передаю индекс как смещение для вызова функции. Индекс без знака, смещение со знаком. Помимо компиляторов (MSVC), которые сбивают с толку -unsigned_value, это означало бы, что мне всегда приходилось беспокоиться о возможном переполнении при преобразовании.

Нет, стандартные комитеты, стоящие за C и C ++, заставляют ваш код вести себя очевидным образом в этом случае: если u - это значение без знака, а s - значение со знаком, где u имеет по крайней мере столько же битов, сколько s , тогда u + s даст математически правильный результат, если u + s переполнится или не заполнится. Если он недостаточен / переполнен, результат будет повторяться, то есть (u + s) mod 2^b , где b - количество битов в u и s . С другой стороны, если подписанный тип может представлять все значения беззнакового типа, то беззнаковое значение будет преобразовано в беззнаковый тип.

Соответствующие пункты в проекте стандарта C11 следующие:

  • 6.2.5.9: Двоичные операции только с беззнаковыми операндами не могут переполняться; результат берется по модулю MAX + 1 , где MAX - наибольшее представимое значение.
  • 6.3.1.3: Для заданного значения со знаком s оно преобразуется в значение без знака s если s >= 0 , в противном случае оно преобразуется в s + MAX + 1 .
  • 6.3.1.8: Знаковые и беззнаковые операнды [одинакового размера] преобразуются в беззнаковые; беззнаковый операнд преобразуется в знаковый тип, если знаковый тип может представлять все значения беззнакового типа

Следовательно, u + s (синтаксис C) будет оцениваться как

  • (u + s) mod (M + 1) если s >= 0 ,
  • (u + s + M + 1) mod (M + 1) противном случае.

В отсутствие переполнения или потери значимости это выражение будет оценивать как u + s что является интуитивно желаемым результатом.

Когда я исследовал это, я наткнулся на признание того, что использование беззнакового типа для индексов контейнера C ++ и, возможно, даже возвращаемого типа метода size () было исторической ошибкой, так как вы быстро заканчиваете тем, что смешиваете числа со знаком и без знака в каким-то образом.

Есть некоторые программисты C ++ (включая изобретателя C ++), которые предлагают использовать целые числа со знаком повсюду, см. Основные принципы C ++, но я бы не назвал это признанием. Проблема с политикой «целые числа со знаком везде» заключается в

  • проверка минимальных значений: при целочисленном без знака проверять минимальное значение во многих случаях излишне, при целочисленном со знаком - обязательно; это подвержено ошибкам и может вызвать проблемы с безопасностью, см., например, CWE-839 _Numeric Range Comparison Without Minimum Check_ .
  • переполнения: беззнаковое переполнение имеет четко определенный результат, тогда как подписанное целочисленное переполнение составляет неопределенное поведение.

Вы можете попытаться проверить подписанное переполнение с помощью выражения a + b < a но компилятор может оптимизировать его без предупреждения, см., Например, ошибку GCC 30475 _assert (int + 100> int), оптимизированную вне 2007 года. Это сработает. с целыми числами без знака ( a unsigned, b возможно, подписанный и b имеющий не более чем a ). Если посмотреть на статью

Была ли эта страница полезной?
0 / 5 - 0 рейтинги

Смежные вопросы

h-vetinari picture h-vetinari  ·  8Комментарии

Dichloromethane picture Dichloromethane  ·  11Комментарии

oxydicer picture oxydicer  ·  6Комментарии

5tefan picture 5tefan  ·  3Комментарии

JHenneberg picture JHenneberg  ·  10Комментарии