Comments 74
Я бы сказал этот трюк допустим для ядра, которое может контролировать своё адресное пространство, но вот для приложений он опасен.
Единственное, что можно извлечь в userland — это проверку на IS_NULL (от 0 до N), где N — какое-то условное значение, при условии, что программа чётко знает, что в этом промежутке нет области данных (с любыми правами доступа). Такую гарантию можно дать только зная скрипт линковщика, и то, если не допустимы перемещения.
Какой смысл? Очень часто бывает, что возвращают адрес смещения поля в структуре, что не проходит проверку на ноль, если сам указатель структуры был нулевой и поле не первое. Обращения по таким адресам на некоторых архитектурах вообще вызывает машинные прерывания с интересными последствиями.
Единственное, что можно извлечь в userland — это проверку на IS_NULL (от 0 до N), где N — какое-то условное значение, при условии, что программа чётко знает, что в этом промежутке нет области данных (с любыми правами доступа). Такую гарантию можно дать только зная скрипт линковщика, и то, если не допустимы перемещения.
Какой смысл? Очень часто бывает, что возвращают адрес смещения поля в структуре, что не проходит проверку на ноль, если сам указатель структуры был нулевой и поле не первое. Обращения по таким адресам на некоторых архитектурах вообще вызывает машинные прерывания с интересными последствиями.
Хоть и истина есть в ваших рассуждениях, но вероятность того, что наш процесс будет занимать области памяти (в виртуальном адресном пространстве, если есть MMU) 0…16 и 2^32-1024…2^32-1 весьма мала, согласитесь.
Соглашусь, но вы ведь не программируете бизнес логику полагаясь на теорию вероятностей? Оптимизация — да, но не контроль ошибок.
При этом в любом случае надо создавать отдельный тип, который ЧЁТКО указывает, что вместо указателя может быть возращено мракобесие.
Пример:
Ну, а лучше вообще union:
При этом в любом случае надо создавать отдельный тип, который ЧЁТКО указывает, что вместо указателя может быть возращено мракобесие.
Пример:
struct MyType;
typedef struct MyType *MyTypeOrError;
Ну, а лучше вообще union:
union MyTypeOrError {
struct MyType *my_type;
ErrorCode error_code;
};
Но как в случае этого union'а определить, какой из результатов актуален?
Вот тогда и начинаем мракобесие с макросами.
Если же С++, то эту кухню можно спрятать в member function union'а, поскольку без выполнения определённых условий union остаётся POD-типом данных. Для всего прочего притянуть template и поставить на поток…
Фантазия может улететь далеко, но в С++ для решения той же задачи есть exceptions, а в C — thread safe errno.
Поэтому, я принципиально против таких интерфейсов.
Если же С++, то эту кухню можно спрятать в member function union'а, поскольку без выполнения определённых условий union остаётся POD-типом данных. Для всего прочего притянуть template и поставить на поток…
Фантазия может улететь далеко, но в С++ для решения той же задачи есть exceptions, а в C — thread safe errno.
Поэтому, я принципиально против таких интерфейсов.
Можно в принципе не полагаться на теорию вероятностей, а посмотреть, как происходит распределение сегментов памяти в программе. По крайней мере NULL — это невалидный указатель. Далее исходим из того, что память ядро выравнивает по 4 килобайта, а также из того, что по младшим адресам всегда помещаются сегменты кода и данных. То есть ядро (и тем более malloc) не будет размещать какие-либо данные как минимум в диапазоне адресов 0-4095.
Ну и /proc/xxxx/maps в помощь. Минус в том, что за все операционные системы ручаться нельзя, да и непонятно, что там на встраиваемых системах. На 64-разрядной машине виртуальная область памяти вообще содержит массу «дырок»:
Ну и /proc/xxxx/maps в помощь. Минус в том, что за все операционные системы ручаться нельзя, да и непонятно, что там на встраиваемых системах. На 64-разрядной машине виртуальная область памяти вообще содержит массу «дырок»:
Скрытый текст
Оставим невесть куда утекающие 87424K «кучи» на совести разработчиков, и обратим внимание, что ядро оставляет приличные области адресного пространства как «сверху», так и «снизу».$ sudo pmap `/usr/bin/pgrep X`
7287: /etc/X11/X :0 -auth /var/run/lightdm/root/:0 -nolisten tcp vt1 -novtswitch
0000000000400000 2032K r-x-- /usr/bin/Xorg
00000000007fb000 8K r---- /usr/bin/Xorg
00000000007fd000 48K rw--- /usr/bin/Xorg
0000000000809000 64K rw--- [ anon ]
0000000000dd3000 87424K rw--- [ anon ]
00007f6f76c5f000 4096K rw-s- [ shmid=0x3aa00022 ]
00007f6f770df000 512K rw-s- [ shmid=0x3ab4001b ]
00007f6f7715f000 384K rw-s- [ shmid=0x3a9b001e ]
00007f6f771bf000 1072K rw-s- [ shmid=0x3a9a801f ]
00007f6f772cb000 384K rw-s- [ shmid=0x3a99001d ]
00007f6f7732b000 8476K rw-s- [ shmid=0x3a97001a ]
00007f6f77b72000 384K rw-s- [ shmid=0x3a8a000d ]
00007f6f77bf2000 384K rw-s- [ shmid=0x3a97801c ]
00007f6f77c52000 384K rw-s- [ shmid=0x3a900019 ]
00007f6f77cb2000 384K rw-s- [ shmid=0x3a830018 ]
00007f6f77d12000 4096K rw-s- [ shmid=0x3a828017 ]
00007f6f78112000 512K rw-s- [ shmid=0x3a820016 ]
00007f6f78192000 384K rw-s- [ shmid=0x3a808015 ]
00007f6f781f2000 384K rw-s- [ shmid=0x3a800013 ]
00007f6f78252000 384K rw-s- [ shmid=0x3a7f000e ]
00007f6f782b2000 512K rw-s- [ shmid=0x3a7b0012 ]
00007f6f78332000 384K rw-s- [ shmid=0x3a798010 ]
00007f6f783b2000 384K rw-s- [ shmid=0x3a8e8014 ]
00007f6f78412000 384K rw-s- [ shmid=0x3a7d800b ]
00007f6f78472000 384K rw-s- [ shmid=0x3a768009 ]
00007f6f784d2000 512K rw-s- [ shmid=0x3a75800c ]
00007f6f78572000 512K rw-s- [ shmid=0x3ab48021 ]
00007f6f785f2000 384K rw-s- [ shmid=0x3a94000f ]
00007f6f78652000 384K rw-s- [ shmid=0x3a73800a ]
00007f6f786b2000 384K rw-s- [ shmid=0x3a728008 ]
00007f6f78712000 512K rw-s- [ shmid=0x3a720007 ]
00007f6f78792000 9004K rw--- [ anon ]
00007f6f7905d000 384K rw-s- [ shmid=0x3a6f8006 ]
00007f6f790bd000 18008K rw--- [ anon ]
00007f6f7a253000 384K rw-s- [ shmid=0x3a6c8002 ]
00007f6f7a2b3000 48K r-x-- /usr/lib64/libnss_files-2.18.so
00007f6f7a2bf000 2044K ----- /usr/lib64/libnss_files-2.18.so
00007f6f7a4be000 4K r---- /usr/lib64/libnss_files-2.18.so
00007f6f7a4bf000 4K rw--- /usr/lib64/libnss_files-2.18.so
00007f6f7a4e1000 48K r-x-- /usr/lib64/xorg/modules/input/evdev_drv.so
00007f6f7a4ed000 2044K ----- /usr/lib64/xorg/modules/input/evdev_drv.so
00007f6f7a6ec000 4K r---- /usr/lib64/xorg/modules/input/evdev_drv.so
00007f6f7a6ed000 4K rw--- /usr/lib64/xorg/modules/input/evdev_drv.so
00007f6f7a6fe000 788K rw-s- /dev/nvidia0
00007f6f7a7c3000 8192K rw-s- /dev/nvidia0
00007f6f7afc3000 188K r-x-- /usr/lib64/xorg/modules/libwfb.so
00007f6f7aff2000 2044K ----- /usr/lib64/xorg/modules/libwfb.so
00007f6f7b1f1000 4K r---- /usr/lib64/xorg/modules/libwfb.so
00007f6f7b1f2000 4K rw--- /usr/lib64/xorg/modules/libwfb.so
00007f6f7b1f3000 144K r-x-- /usr/lib64/xorg/modules/libfb.so
00007f6f7b217000 2044K ----- /usr/lib64/xorg/modules/libfb.so
00007f6f7b416000 4K r---- /usr/lib64/xorg/modules/libfb.so
00007f6f7b417000 4K rw--- /usr/lib64/xorg/modules/libfb.so
00007f6f7b418000 7156K r-x-- /usr/lib64/nvidia-current/xorg/nvidia_drv.so
00007f6f7bb15000 2048K ----- /usr/lib64/nvidia-current/xorg/nvidia_drv.so
00007f6f7bd15000 784K rw--- /usr/lib64/nvidia-current/xorg/nvidia_drv.so
00007f6f7bdd9000 444K rw--- [ anon ]
00007f6f7be48000 28064K r-x-- /usr/lib64/nvidia-current/libnvidia-glcore.so.331.113
00007f6f7d9b0000 2044K ----- /usr/lib64/nvidia-current/libnvidia-glcore.so.331.113
00007f6f7dbaf000 10796K rwx-- /usr/lib64/nvidia-current/libnvidia-glcore.so.331.113
00007f6f7e63a000 116K rwx-- [ anon ]
00007f6f7e657000 12K r-x-- /usr/lib64/nvidia-current/tls/libnvidia-tls.so.331.113
00007f6f7e65a000 2044K ----- /usr/lib64/nvidia-current/tls/libnvidia-tls.so.331.113
00007f6f7e859000 4K rw--- /usr/lib64/nvidia-current/tls/libnvidia-tls.so.331.113
00007f6f7e85a000 9480K r-x-- /usr/lib64/nvidia-current/xorg/libglx.so.331.113
00007f6f7f19c000 2048K ----- /usr/lib64/nvidia-current/xorg/libglx.so.331.113
00007f6f7f39c000 2404K rwx-- /usr/lib64/nvidia-current/xorg/libglx.so.331.113
00007f6f7f5f5000 16K rwx-- [ anon ]
00007f6f7f5f9000 16K r-x-- /usr/lib64/xorg/modules/drivers/v4l_drv.so
00007f6f7f5fd000 2044K ----- /usr/lib64/xorg/modules/drivers/v4l_drv.so
00007f6f7f7fc000 4K r---- /usr/lib64/xorg/modules/drivers/v4l_drv.so
00007f6f7f7fd000 4K rw--- /usr/lib64/xorg/modules/drivers/v4l_drv.so
00007f6f7f7fe000 84K r-x-- /usr/lib64/libgcc_s-4.8.2.so.1
00007f6f7f813000 2044K ----- /usr/lib64/libgcc_s-4.8.2.so.1
00007f6f7fa12000 4K r---- /usr/lib64/libgcc_s-4.8.2.so.1
00007f6f7fa13000 4K rw--- /usr/lib64/libgcc_s-4.8.2.so.1
00007f6f7fa14000 404K r-x-- /usr/lib64/libpcre.so.1.2.1
00007f6f7fa79000 2044K ----- /usr/lib64/libpcre.so.1.2.1
00007f6f7fc78000 4K r---- /usr/lib64/libpcre.so.1.2.1
00007f6f7fc79000 4K rw--- /usr/lib64/libpcre.so.1.2.1
00007f6f7fc7a000 128K r-x-- /usr/lib64/libgraphite2.so.3.0.1
00007f6f7fc9a000 2044K ----- /usr/lib64/libgraphite2.so.3.0.1
00007f6f7fe99000 8K r---- /usr/lib64/libgraphite2.so.3.0.1
00007f6f7fe9b000 4K rw--- /usr/lib64/libgraphite2.so.3.0.1
00007f6f7fe9c000 1016K r-x-- /usr/lib64/libglib-2.0.so.0.3800.2
00007f6f7ff9a000 2044K ----- /usr/lib64/libglib-2.0.so.0.3800.2
00007f6f80199000 4K r---- /usr/lib64/libglib-2.0.so.0.3800.2
00007f6f8019a000 4K rw--- /usr/lib64/libglib-2.0.so.0.3800.2
00007f6f8019b000 4K rw--- [ anon ]
00007f6f8019c000 324K r-x-- /usr/lib64/libharfbuzz.so.0.922.0
00007f6f801ed000 2048K ----- /usr/lib64/libharfbuzz.so.0.922.0
00007f6f803ed000 4K r---- /usr/lib64/libharfbuzz.so.0.922.0
00007f6f803ee000 4K rw--- /usr/lib64/libharfbuzz.so.0.922.0
00007f6f803ef000 376K r-x-- /usr/lib64/libpng16.so.16.16.0
00007f6f8044d000 2044K ----- /usr/lib64/libpng16.so.16.16.0
00007f6f8064c000 4K r---- /usr/lib64/libpng16.so.16.16.0
00007f6f8064d000 4K rw--- /usr/lib64/libpng16.so.16.16.0
00007f6f8064e000 24K r-x-- /usr/lib64/libfontenc.so.1.0.0
00007f6f80654000 2044K ----- /usr/lib64/libfontenc.so.1.0.0
00007f6f80853000 4K r---- /usr/lib64/libfontenc.so.1.0.0
00007f6f80854000 4K rw--- /usr/lib64/libfontenc.so.1.0.0
00007f6f80855000 4K rw--- [ anon ]
00007f6f80856000 60K r-x-- /usr/lib64/libbz2.so.1.0.6
00007f6f80865000 2044K ----- /usr/lib64/libbz2.so.1.0.6
00007f6f80a64000 4K r---- /usr/lib64/libbz2.so.1.0.6
00007f6f80a65000 4K rw--- /usr/lib64/libbz2.so.1.0.6
00007f6f80a66000 604K r-x-- /usr/lib64/libfreetype.so.6.11.3
00007f6f80afd000 2044K ----- /usr/lib64/libfreetype.so.6.11.3
00007f6f80cfc000 24K r---- /usr/lib64/libfreetype.so.6.11.3
00007f6f80d02000 4K rw--- /usr/lib64/libfreetype.so.6.11.3
00007f6f80d03000 96K r-x-- /usr/lib64/libz.so.1.2.8
00007f6f80d1b000 2048K ----- /usr/lib64/libz.so.1.2.8
00007f6f80f1b000 4K r---- /usr/lib64/libz.so.1.2.8
00007f6f80f1c000 4K rw--- /usr/lib64/libz.so.1.2.8
00007f6f80f1d000 28K r-x-- /usr/lib64/librt-2.18.so
00007f6f80f24000 2044K ----- /usr/lib64/librt-2.18.so
00007f6f81123000 4K r---- /usr/lib64/librt-2.18.so
00007f6f81124000 4K rw--- /usr/lib64/librt-2.18.so
00007f6f81125000 1712K r-x-- /usr/lib64/libc-2.18.so
00007f6f812d1000 2044K ----- /usr/lib64/libc-2.18.so
00007f6f814d0000 16K r---- /usr/lib64/libc-2.18.so
00007f6f814d4000 8K rw--- /usr/lib64/libc-2.18.so
00007f6f814d6000 16K rw--- [ anon ]
00007f6f814da000 1032K r-x-- /usr/lib64/libm-2.18.so
00007f6f815dc000 2044K ----- /usr/lib64/libm-2.18.so
00007f6f817db000 4K r---- /usr/lib64/libm-2.18.so
00007f6f817dc000 4K rw--- /usr/lib64/libm-2.18.so
00007f6f817dd000 20K r-x-- /usr/lib64/libXdmcp.so.6.0.0
00007f6f817e2000 2044K ----- /usr/lib64/libXdmcp.so.6.0.0
00007f6f819e1000 4K r---- /usr/lib64/libXdmcp.so.6.0.0
00007f6f819e2000 4K rw--- /usr/lib64/libXdmcp.so.6.0.0
00007f6f819e3000 8K r-x-- /usr/lib64/libXau.so.6.0.0
00007f6f819e5000 2048K ----- /usr/lib64/libXau.so.6.0.0
00007f6f81be5000 4K r---- /usr/lib64/libXau.so.6.0.0
00007f6f81be6000 4K rw--- /usr/lib64/libXau.so.6.0.0
00007f6f81be7000 236K r-x-- /usr/lib64/libXfont.so.1.4.1
00007f6f81c22000 2048K ----- /usr/lib64/libXfont.so.1.4.1
00007f6f81e22000 4K r---- /usr/lib64/libXfont.so.1.4.1
00007f6f81e23000 8K rw--- /usr/lib64/libXfont.so.1.4.1
00007f6f81e25000 652K r-x-- /usr/lib64/libpixman-1.so.0.32.4
00007f6f81ec8000 2048K ----- /usr/lib64/libpixman-1.so.0.32.4
00007f6f820c8000 28K r---- /usr/lib64/libpixman-1.so.0.32.4
00007f6f820cf000 4K rw--- /usr/lib64/libpixman-1.so.0.32.4
00007f6f820d0000 44K r-x-- /usr/lib64/libdrm.so.2.4.0
00007f6f820db000 2044K ----- /usr/lib64/libdrm.so.2.4.0
00007f6f822da000 4K r---- /usr/lib64/libdrm.so.2.4.0
00007f6f822db000 4K rw--- /usr/lib64/libdrm.so.2.4.0
00007f6f822dc000 96K r-x-- /usr/lib64/libpthread-2.18.so
00007f6f822f4000 2044K ----- /usr/lib64/libpthread-2.18.so
00007f6f824f3000 4K r---- /usr/lib64/libpthread-2.18.so
00007f6f824f4000 4K rw--- /usr/lib64/libpthread-2.18.so
00007f6f824f5000 16K rw--- [ anon ]
00007f6f824f9000 32K r-x-- /usr/lib64/libpciaccess.so.0.11.1
00007f6f82501000 2044K ----- /usr/lib64/libpciaccess.so.0.11.1
00007f6f82700000 4K r---- /usr/lib64/libpciaccess.so.0.11.1
00007f6f82701000 4K rw--- /usr/lib64/libpciaccess.so.0.11.1
00007f6f82702000 12K r-x-- /usr/lib64/libdl-2.18.so
00007f6f82705000 2044K ----- /usr/lib64/libdl-2.18.so
00007f6f82904000 4K r---- /usr/lib64/libdl-2.18.so
00007f6f82905000 4K rw--- /usr/lib64/libdl-2.18.so
00007f6f82906000 1884K r-x-- /usr/lib64/libcrypto.so.1.0.0
00007f6f82add000 2048K ----- /usr/lib64/libcrypto.so.1.0.0
00007f6f82cdd000 104K r---- /usr/lib64/libcrypto.so.1.0.0
00007f6f82cf7000 44K rw--- /usr/lib64/libcrypto.so.1.0.0
00007f6f82d02000 16K rw--- [ anon ]
00007f6f82d06000 64K r-x-- /usr/lib64/libudev.so.1.4.0
00007f6f82d16000 2048K ----- /usr/lib64/libudev.so.1.4.0
00007f6f82f16000 4K r---- /usr/lib64/libudev.so.1.4.0
00007f6f82f17000 4K rw--- /usr/lib64/libudev.so.1.4.0
00007f6f82f18000 120K r-x-- /usr/lib64/ld-2.18.so
00007f6f82f63000 4K rw-s- /dev/nvidia0
00007f6f82f64000 4K rw-s- /dev/nvidia0
00007f6f82f65000 4K rw-s- /dev/nvidia0
00007f6f82f66000 4K rw-s- /dev/nvidia0
00007f6f82f67000 4K rw-s- /dev/nvidia0
00007f6f82f68000 4K rw-s- /dev/nvidia0
00007f6f82f69000 4K rw-s- /dev/nvidia0
00007f6f82f6a000 4K rw-s- /dev/nvidia0
00007f6f82f6b000 4K rw-s- /dev/nvidia0
00007f6f82f6c000 4K rw-s- /dev/nvidia0
00007f6f82f6d000 384K rw-s- [ shmid=0x3a6f0005 ]
00007f6f82fcd000 384K rw-s- [ shmid=0x3a6e8004 ]
00007f6f8302d000 384K rw-s- [ shmid=0x3a6e0003 ]
00007f6f8308d000 64K rw-s- /dev/nvidia0
00007f6f8309d000 128K rw-s- /dev/nvidia0
00007f6f830bd000 348K rw--- [ anon ]
00007f6f83114000 4K rw-s- /dev/nvidia0
00007f6f83115000 4K rw-s- /dev/nvidia0
00007f6f83116000 4K rw-s- /dev/nvidia0
00007f6f83117000 4K rw-s- /dev/nvidia0
00007f6f83118000 4K rw-s- /dev/nvidia0
00007f6f83119000 4K rw-s- /dev/nvidia0
00007f6f8311a000 4K rw-s- /dev/nvidia0
00007f6f8311b000 4K rw-s- /dev/nvidia0
00007f6f8311c000 4K rw-s- /dev/nvidia0
00007f6f8311d000 4K rw-s- /dev/nvidia0
00007f6f8311e000 4K rw-s- /dev/nvidia0
00007f6f8311f000 4K rw-s- /dev/nvidia0
00007f6f83120000 4K rw-s- /dev/nvidia0
00007f6f83121000 68K rw-s- /dev/nvidia0
00007f6f83132000 4K rw-s- /dev/nvidia0
00007f6f83133000 4K rw-s- /dev/nvidia0
00007f6f83134000 8K rw--- [ anon ]
00007f6f83136000 4K r---- /usr/lib64/ld-2.18.so
00007f6f83137000 4K rw--- /usr/lib64/ld-2.18.so
00007f6f83138000 4K rw--- [ anon ]
00007fff52412000 356K rw--- [ stack ]
00007fff524b9000 8K r-x-- [ anon ]
ffffffffff600000 4K r-x-- [ anon ]
Тут есть тонкости. Скажем на 32-битных OS'ях вам никогда не выделят ничего в диапазоне 2^32-1024…2^32-1 потому что там ядро OS живёт. А вот на системе с 64-битным ядром — такое уже может случиться. Нужно просто постараться «забрать себе» эту страничку. Как можно раньше после запуска программы. Если её не дали (32-битная OS) — то и никому её не дадут, если дали — то таки дали (и тоже больше уже никому не дадут). В идеале это вообще loader должен бы сделать, но его мы не можем контролировать (хотя если хочется 200% надёжности можно сделать как в nacl_bootsrap.
Какая к бесу теория вероятностй? При старте программы пытаетесь сходу аллоцировать первую и последнюю страницы. И всё. Получилось — они ваши, больше никаких объектов там не будет, не получилось — значит они не ваши (так на всех современных 32-битных OS'ах будет), но это же и значит, что больше вам там ничего не дадут. Всё. Делов-то.
Это вы сейчас о чем?
О user-программах, или о ядре?
Если о ядре. Допустим даже способ на х86 рабочий. Но кто гарантирует, что в других архитектурах оно не ляжет неблагоприятно?
Если о пользовательских программах — все сложней, чем вы написали. Даже если при старте захватить страницу не вышло, кто-нибудь эту страницу может когда-нибудь отпустить, она станет свободной. Потом ваш процесс запросит память — отдастся эта страница. А вы полагаетесь на то, что она вам не отдастся никогда.
О user-программах, или о ядре?
Если о ядре. Допустим даже способ на х86 рабочий. Но кто гарантирует, что в других архитектурах оно не ляжет неблагоприятно?
Если о пользовательских программах — все сложней, чем вы написали. Даже если при старте захватить страницу не вышло, кто-нибудь эту страницу может когда-нибудь отпустить, она станет свободной. Потом ваш процесс запросит память — отдастся эта страница. А вы полагаетесь на то, что она вам не отдастся никогда.
Даже если при старте захватить страницу не вышло, кто-нибудь эту страницу может когда-нибудь отпустить, она станет свободной.Угу. А ещё нас всех могут захватить инопланетяне. Нет, есть, конечно, шанс, что когда вы применяете эту технологию не в программе, а в библиотеке, что ядро выделит кому-то вот прямо-таки последнюю доступную страничку, а потом этот кто-то эту страничку отпустит, но это какие-то ужасы из разряда попадания кометой прямо вам в лоб. Даже в системах, где доступны все 4GiB ядро вряд ли будет так запросто эту страницу выделять без крайней нужды (не тот регион), так что тут мы боремся уже не случайным распределением памяти, а с какими-то вредителями, которые вас пытаются обмануть. Причём эти вредители живут прямо в адресном пространстве вашего приложение и, в общем-то, могут докучать вам миллионов разных других способов.
А вы полагаетесь на то, что она вам не отдастся никогда.Если она вам не отдалась на старте процесса — то она вам не отдастся уже никогда, поверьте. С библиотеками чуть сложнее, но даже и там вероятность того, что кто-то получит эту страничку но потом-таки отдаст её вам исчезающе мала. Но если уж вам и с этой веростностью мириться не хочется — перекройте
malloc
и ::operator new
для вашей библиотеки.А ещё лучше использовать положительные коды ошибок, (как предложил chabapok) так как все современные OS несколько килобайт в районе NULL'а резервируют сами, без всяких ваших вмещательств.
Вообще-то, я и есть chabapok. Но вобщем, это сейчас не важно, т.к. речь о том, что в ядре под ошибки выделяют именно верхний диапазон. Я специально посмотрел код последнего стабильного ядра.
Вы оперируете таким понятием, как «исчезающе малая вероятность». Действительно, программисты иногда полагаются на вероятности. Например, uuid считается, что практически никогда не повторится, и им реально пользуются, и работает он хорошо.
Но по моему мнению, нету никаких оснований считать вероятность пренебрежимо маленькой в нашем конкретном примере.
Сами посудите:
— архитектур бывает много. Ядро же собирают не только под х86 и х86_64, но и под еще около 10архитектур.
— memory manager-ов бывает тоже много. Некоторые из них работают по приницпу чанков, которые циклически выделяются. Если будет такой и страница в него попадет — она обязательно отдастся в приложение когда-нибудь в обозримом будущем. К тому же достоверно известно, что отдача ранее использованных страниц — это потенциальная возможность кражи паролей, которую разработчики не игнорируют.
— вообще необязательно, чтобы речь шла о памяти. Например, в arm верхнее адресное пространство (это инфа не 100%) выделено под периферию. Т.е., адреса валидные и считай что уже выделены и используются не для ошибок. При этом в разных железках конкретная периферия может быть вшита в разные адресные пространства. Если драйвер такого периферийного устройства будет написан с использованием этих макросов, в железке устройство нельзя располагать по высоким адресам. Это совершенно неочевидный момент.
По этим причинам, тезис «если она вам не отдалась на старте процесса — то она вам не отдастся уже никогда» считаю в общем случае невалиден. По всей видимости, на х86 он работает хорошо.
Вы оперируете таким понятием, как «исчезающе малая вероятность». Действительно, программисты иногда полагаются на вероятности. Например, uuid считается, что практически никогда не повторится, и им реально пользуются, и работает он хорошо.
Но по моему мнению, нету никаких оснований считать вероятность пренебрежимо маленькой в нашем конкретном примере.
Сами посудите:
— архитектур бывает много. Ядро же собирают не только под х86 и х86_64, но и под еще около 10архитектур.
— memory manager-ов бывает тоже много. Некоторые из них работают по приницпу чанков, которые циклически выделяются. Если будет такой и страница в него попадет — она обязательно отдастся в приложение когда-нибудь в обозримом будущем. К тому же достоверно известно, что отдача ранее использованных страниц — это потенциальная возможность кражи паролей, которую разработчики не игнорируют.
— вообще необязательно, чтобы речь шла о памяти. Например, в arm верхнее адресное пространство (это инфа не 100%) выделено под периферию. Т.е., адреса валидные и считай что уже выделены и используются не для ошибок. При этом в разных железках конкретная периферия может быть вшита в разные адресные пространства. Если драйвер такого периферийного устройства будет написан с использованием этих макросов, в железке устройство нельзя располагать по высоким адресам. Это совершенно неочевидный момент.
По этим причинам, тезис «если она вам не отдалась на старте процесса — то она вам не отдастся уже никогда» считаю в общем случае невалиден. По всей видимости, на х86 он работает хорошо.
Например, в arm верхнее адресное пространство (это инфа не 100%) выделено под периферию.Это сильно зависит от семейства.
Современным Cortex-A (из ARMv7-AR и ARMv8) старший кусок 32/36/40-битного адресного пространства занимает DRAM: infocenter.arm.com/help/topic/com.arm.doc.den0001c/DEN0001C_principles_of_arm_memory_maps.pdf
Современные Cortex-M (из ARMv6-M и ARMv7-M) старший кусок 32-битного адресного пространства занимает vendor-specific peripheral: infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0203h/BEIFDEEB.html
На более древних ARM9 и ARM7, если не ошибаюсь, там тоже периферия.
А в жизни обычно приходится ковыряться вот в этом: developer.gnome.org/glib/stable/glib-Error-Reporting.html Т.е. мы имеем: код возврата, указатель на объект, который хотим получить и указатель на объект «Ошибка», если код возврата false, после которого ещё надо память освобождать. Но зато можно передавать произвольные ошибки с произвольными сообщениями об ошибках.
Применяли похожее решение в своем проекте — функция принимала указатель на структуру прямоугольника. Все значения меньше 64к считались просто числом, и трактовались как прямоугольник с равными сторонами.
Похожее решение использует GetProcAddress — младшее слово может содержать точный номер функции из импорта вместо строки.
Похожее решение использует GetProcAddress — младшее слово может содержать точный номер функции из импорта вместо строки.
Это неопределённое поведение, точка.
В Linux ядре многое что может показаться странным, но, как писали некоторые, это все проверено и работает.
Почитайте habrahabr.ru/company/pvs-studio/blog/250701/
Почитайте habrahabr.ru/company/pvs-studio/blog/250701/
На каком этапе? Я честно говоря не представляю где.
Единственные всюду верные предположения о значении указателей — это NULL==0 и арифметика указателей. Все остальное работает не везде, в частности на x86-64 этот код может и не сработать. А если попробовать скомпилировать его под PDP-11… Будет весело.
linux не делает всюду верных предположений. Он заточен под небольшой набор компиляторов и архитектур и завязан на их реализации. До недавнего времени собрать его чем-то отличным от gcc была проблема.
Это определяется ABI. Вообще, судя по комментариям и статьям, люди спорят, забыв напрочь, что над стандартом стоит ABI.
Но зачем привязывать программу к ABI? Нафига? Стандарт для того и писали, чтобы подобной фигней не занимались.
Программа работает не в виртуальном пространстве, а на реальном железе. Всё определяется железом, если мы не рассматриваем виртуальные машины. Так что как раз стандарт ЯП побоку, если он противоречит архитектуре.
А если вашу программу потом придется портировать на какую-нибудь новую архитектуру? Вы вообще не допускаете такой возможности? А если делать это будет склонный к насилию психопат, который знает, где вы живете? ©
Ну, как видите конца света не наступило, так что и новые архитектуры, будучи изобретёнными, будут учитывать особенности и стандартов, и де факто существующих на рынке ОС.
Стандарт писали для написания переносимых программ. Но переносимость — она штука относительная. Если вас всякие HP-UXы не интересуют и вполне хватает поддержки Linux/MacOS/Windows, то описанный в статье приём годится вполне. Особенно если использовать не отрицательные коды ошибок, как в ядре, а положительные.
Может быть вы перепутали неопределённое поведение с поведением, зависящим от реализации, вопросительный знак.
А что это за диапазон такой странный — который третий по счету? это же пределами 32 бит.
Получается, такое может работать только на 64битном ядре, или как?
И еще такой вопрос: что значит ваш пассаж «люди начали ими пользоваться!»? Неужели Торвальдс принял от вас коммит с такими изменениями?
Получается, такое может работать только на 64битном ядре, или как?
И еще такой вопрос: что значит ваш пассаж «люди начали ими пользоваться!»? Неужели Торвальдс принял от вас коммит с такими изменениями?
Число 0xffffffff-MAX_ERRNO+1 — меньше 2^32, так что не за пределами.
Так это у вас в посте не тире, а знак минус? Тогда понятно.
В сорцах "-" (минус), парсер формы поставил "—" тире автоматически. Исправил на raw code. Спасибо!
А что насчет второго вопроса? Ну пускай не Торвальдс, но неужели кто-то это принял?
Лучше тогда уж так:
0 — это null
1...MAX_ERRNO — это ошибки. может даже они поместятся в диапазон традиционного null.
все остальное — это валидные адреса.
потому, что ваш метод с 64 битной архитектурой в лучшем случае некрасив, а в худшем нерабочий.
Лучше тогда уж так:
0 — это null
1...MAX_ERRNO — это ошибки. может даже они поместятся в диапазон традиционного null.
все остальное — это валидные адреса.
потому, что ваш метод с 64 битной архитектурой в лучшем случае некрасив, а в худшем нерабочий.
Диапазон читается как
(0xffffffff-MAX_ERRNO+1; 0xffffffff)
Левое число меньше 2^32.
(0xffffffff-MAX_ERRNO+1; 0xffffffff)
Левое число меньше 2^32.
Есть ещё старый трюк с tagged pointers, но он работает только для структур с подходящим выравниванием. Т.е. указатель на char не пометишь. Зато при должной осторожности вполне подходит и для юзер-спейса (и довольно часто используется в рантаймах языков высокого уровня).
Как прострелить себе ногу бесплатно без СМС.
За такие статьи для широкой аудитории я бы давал ридонли на год.
За такие статьи для широкой аудитории я бы давал ридонли на год.
Ну, значит, Торвальдс сидел бы в ro. :)
заглянте в linux-3.19.1/tools/virtio/linux/err.h
заглянте в linux-3.19.1/tools/virtio/linux/err.h
Я наверное погорячился. Пусть каждый стрельнёт себе в ногу. Пурка па?
Сишников никогда не останавливала опасность выстрелить себе в ногу! И если ты это сделал — ты ссзб. Если хочется уберечься от самострела, используйте java :)
А вообще конечно странно. Мне тоже несовсем ясно, как подобное оказалось в ядре. Допустим даже на х86 оно рабочее, но ведь ядро собирают под множество архитектур. И в какой-нибудь из них верхние адреса памяти вполне могут быть задействованы под валидные данные.
А вообще конечно странно. Мне тоже несовсем ясно, как подобное оказалось в ядре. Допустим даже на х86 оно рабочее, но ведь ядро собирают под множество архитектур. И в какой-нибудь из них верхние адреса памяти вполне могут быть задействованы под валидные данные.
И в какой-нибудь из них верхние адреса памяти вполне могут быть задействованы под валидные данные.С какого перепугу? Ядро само решает — что и куда оно будет класть. Вариантов ровно два: либа эта страничка занята обычной памятью — и тогда просто ядро потеряет 4K этой памяти, чтобы валидные объекты там не образовывались, либо там какая-нибудь хитрая хрень — и тогда обычные объекты там точно быть размещены не могут. В обоих случаях всё гарантированно работает.
Я думаю это всё же контроль ошибок для объектов ядра. А объекты берутся явно не с потолка, для них сначала выделяют память через kmalloc, который не должен выделять адреса из пространства, зарезервированного под коды ошибок, так как сам возвращает коды ошибок из этого диапазона — круг замкнулся. По сути нужно лишь гарантировать, что malloc не вернёт адрес из области 0-MAX_ERRNO, а это достаточно просто.
Ну если так, то да.
Но стоит ли сэкономленая память этого куска ~4кб вверху — это тоже вопрос, кстати. Тем более, что err выделяется обычно или в стеке, где занимает (даже с учетом теоретически возможной «нехватки» регистров процессора) никак не 4кб, или это вообще 1 ячейка на поток.
Вот кстати тоже интересный вопрос. Утверждается, что трюк позволяет убыстрить и(или) уменьшить размер в памяти. Но это лишь абстрактные слова, а где численные оценки достигнутого эффекта для разных архитектур?
Но стоит ли сэкономленая память этого куска ~4кб вверху — это тоже вопрос, кстати. Тем более, что err выделяется обычно или в стеке, где занимает (даже с учетом теоретически возможной «нехватки» регистров процессора) никак не 4кб, или это вообще 1 ячейка на поток.
Вот кстати тоже интересный вопрос. Утверждается, что трюк позволяет убыстрить и(или) уменьшить размер в памяти. Но это лишь абстрактные слова, а где численные оценки достигнутого эффекта для разных архитектур?
Тем более, что err выделяется обычно или в стеке, где занимает (даже с учетом теоретически возможной «нехватки» регистров процессора) никак не 4кб, или это вообще 1 ячейка на поток.Тут вопрос не в экономии на ячейках памяти, а в количестве использованных регистров.
Утверждается, что трюк позволяет убыстрить и(или) уменьшить размер в памяти. Но это лишь абстрактные слова, а где численные оценки достигнутого эффекта для разных архитектур?А с этим у подобных трюков всегда беда. Понятно, что когда их придумывали (20 лет назад!), то скорость таки меряли. Но с тех пор и процессоры изменились и компиляторы стали умнее — а померить заново ничего нельзя: уж слишком много на этот подход теперь завязано!
С другой стороны… В ядре так просто ничего не поменять, но сделать какие-нибудь просты измерения было бы интересно… уж по крайней мере полезно это сделать перед тем как начинать этот трюк у себя в программе применять.
отведение 1 регистра под errno исключает его из некоторых мест. В результате добавляются push/pop — растет память и падает быстродействие. Но это происходит только в тех случаях, когда регистров перестает хватать. Если регистров хватает, то все остается как есть (при условии, что компилятор нормальный). А как часто случается, что регистров не хватает? Неизвестно. Это предмет для исследовательской работы.
Насчет 20лет назад. Действительно, это вполне может быть исторически сложившийся стиль.
Насчет 20лет назад. Действительно, это вполне может быть исторически сложившийся стиль.
А как часто случается, что регистров не хватает? Неизвестно. Это предмет для исследовательской работы.Угу. Проблема в том, что эта самая «исследовательская работа» случилась через много лет после появления Linux'а. Когда Linux появился GCC более-менее сносно себя вёл на m68k (у которого 8 обычных регистров и ещё 7 адресных, которые тоже могут участвовать во многих арифметических операциях), а на x86 он очень любил всё складывать в стек.
Отсюда, кстати, и использование последней странички и многое другое. Не забудьте слова Линуса про то, что «It is NOT portable (uses 386 task switching etc), and it probably never will support anything other than AT-harddisks, as that's all I have :-(»
Никаких планов сделать что-то супер-пупер-кроссплатформенное у Линуса не было :-) Это вам не ультрасуперпереносимое ядро NT с поддержкой ажно трёх процессоров (IA32, x86-64 и ARMа), где тысячи разработчиков трахаются из-за решений сделанных для поддержки процессоров на которых операционка уже давно не работает.
для них сначала выделяют память через kmalloc, который не должен выделять адреса из пространства, зарезервированного под коды ошибок, так как сам возвращает коды ошибок из этого диапазона — круг замкнулся
Нет, kmalloc не возвращает коды ошибок в указателях. Он возвращает либо валидный указатель, либо NULL.
Не совсем в тему и больше к плюсам, для компактного и удобного выставления кодов ошибки в bool-методах классов использовал
пару процедур
и что-то типа
Тогда код получается довольно элегантным:
Аналогичный код можно использовать и для методов типа void. Тогда соответственно и error() и success() возвращают void.
bool error(int code); //всегда возвращает false
bool success(); // всегда возвращает true
и что-то типа
int lastError();
Тогда код получается довольно элегантным:
bool SomeClass::doSomething()
{
if(something_wrong)
return error(something_wrong_code);
if(something_happened)
return error(something_happened_code)
do_something();
return success();
}
Аналогичный код можно использовать и для методов типа void. Тогда соответственно и error() и success() возвращают void.
Кстати вот еще вопрос.
Наглядность оно улучшает. Это да. Но в статье утверждается, что этот трюк позволяет повысить производительность и(или) экономить память. Пока нету тестов, очевидно, что это утверждение является оценочным суждением.
Хочется увидеть численные результаты эффекта, достигнутого за счет использования подобного стиля. Для различных платформ/компиляторов.
Наглядность оно улучшает. Это да. Но в статье утверждается, что этот трюк позволяет повысить производительность и(или) экономить память. Пока нету тестов, очевидно, что это утверждение является оценочным суждением.
Хочется увидеть численные результаты эффекта, достигнутого за счет использования подобного стиля. Для различных платформ/компиляторов.
Тот камент был адресный. Не думал, что вы его прочтете.
ABI какого компилятора так делает?
Вообще-то, компилятор должен начать использовать стек только если не хватает регистров, или если в функции берется адрес переменной. И то, если такая переменная не volatile, то компилятор может заоптимайзить. Я не скажу как поступает gcc для x86 и не скажу про clang, но когда-то я смотрел какой-то компилятор для arm от texas instrumnts — и там все было максимально регистровым.
Насчет замечаний про кэш. Вызов функции (хотя это может зависеть от архитектуры), это засовывание в стек адреса возврата. А возврат из функции — это соответственно, чтение адреса возврата из стека. По этой причине, размещение дополнительной переменной в стеке в большинстве случаев не должно создавать нагрузок, связаных с кэшем, т.к. стек и так должен быть в кэше.
Конечно, если сильно постараться, то можно сделать прогу, которая только и будет делать, что подтягивать в кэш данные ради errno. Но стараться придется очень сильно :)
Мое мнение — трюк не сэкономит быстродействия (тем более в большинстве случаев там однократный вызов) и не даст сколь-нибудь стоящего выигрыша в памяти. Он использован в большей степени исключительно ради наглядности.
ABI какого компилятора так делает?
Вообще-то, компилятор должен начать использовать стек только если не хватает регистров, или если в функции берется адрес переменной. И то, если такая переменная не volatile, то компилятор может заоптимайзить. Я не скажу как поступает gcc для x86 и не скажу про clang, но когда-то я смотрел какой-то компилятор для arm от texas instrumnts — и там все было максимально регистровым.
Насчет замечаний про кэш. Вызов функции (хотя это может зависеть от архитектуры), это засовывание в стек адреса возврата. А возврат из функции — это соответственно, чтение адреса возврата из стека. По этой причине, размещение дополнительной переменной в стеке в большинстве случаев не должно создавать нагрузок, связаных с кэшем, т.к. стек и так должен быть в кэше.
Конечно, если сильно постараться, то можно сделать прогу, которая только и будет делать, что подтягивать в кэш данные ради errno. Но стараться придется очень сильно :)
Мое мнение — трюк не сэкономит быстродействия (тем более в большинстве случаев там однократный вызов) и не даст сколь-нибудь стоящего выигрыша в памяти. Он использован в большей степени исключительно ради наглядности.
У меня такое впечатление, что вы не понимаете, что такое ABI.
В остальном, если сделаете такие измерения, то я обязательно вставлю ссылку в статью.
В остальном, если сделаете такие измерения, то я обязательно вставлю ссылку в статью.
Понимаю так: abi — это интерфейс, а не реализация. Т.е., нет никакого единого стандарта для всех компиляторов и всех платформ. Есть некоторое кол-во подходов, которые зарекомендовали себя, как хорошие, но не более. В каждом компиляторе своя реализация. плюс еще есть оптимизация и прагмы, которыми можно настроить компиляцию. Необязательно, что после глубокой оптимизации аби сохранится. Хотя в нормальном компиляторе будет документирована что он сделает в оптимизациях и что нет.
Насчет измерений. Это ж не я утверждал, что подход экономит быстродействие и память. Я утверждал совсем другое — что экономии нет. Если я и сделаю какие-то измерения — они покажут именно это :)
Корректно измерить быстродействие на сегодняшних cpu не представляется возможным, т.к. время выполнения команд недокументировано. Даже на архитектурах, где заявлено что одна команда выполняется за 1 такт, на практике это бывает не всегда так.
Измерять экономию памяти — скучно. Там и так ясно, что экономии или нет, или мало. Выше высказали предположение, что реальная экономия могла быть на старых cpu до 90ых годов. Этот тезис весьма убедителен.
Насчет измерений. Это ж не я утверждал, что подход экономит быстродействие и память. Я утверждал совсем другое — что экономии нет. Если я и сделаю какие-то измерения — они покажут именно это :)
Корректно измерить быстродействие на сегодняшних cpu не представляется возможным, т.к. время выполнения команд недокументировано. Даже на архитектурах, где заявлено что одна команда выполняется за 1 такт, на практике это бывает не всегда так.
Измерять экономию памяти — скучно. Там и так ясно, что экономии или нет, или мало. Выше высказали предположение, что реальная экономия могла быть на старых cpu до 90ых годов. Этот тезис весьма убедителен.
Понимаю так: abi — это интерфейс, а не реализацияЭто, я извиняюсь, как? ABI — это ABI. Читаете документацию, там всё написано — на каких регистрах передаются аргументы, что, куда, и когда сохраняется. Это только в мире DOS разработчики могли сами себе ABI придумывать.
В каждом компиляторе своя реализация.Не в компиляторе, а в операционной системе.
Плюс еще есть оптимизация и прагмы, которыми можно настроить компиляцию.Прагмы могут влиять на ABI, оптимизации — разумеется, нет. Как вы результат компиляции собиратесь из нескольких модулей собирать, если вы в результате оптимизации испортили конвенцию о вызовах?
Измерять экономию памяти — скучно. Там и так ясно, что экономии или нет, или мало.Ну уж нет. Мало того, что стек ядра за 20 лет не изменился (как был 4KiB-8KiB, так и остался), так ещё и L1 кеш процессора вырос всего-то в четыре раза за эти 20 лет (16K в PowerPC 603 в 1994м, 64K в Inte Broadwell в 2014м). Тут, извините, закон Мура не действует. От слова «совсем».
Именно то что вы дали — это же оно и есть. Вы дали ссылку на 19 разных реализаций abi.
«Как вы результат компиляции собиратесь из нескольких модулей собирать, если вы в результате оптимизации испортили конвенцию о вызовах?»
Во-первых, конвенцию о вызовах надо соблюдать только когда зовешь внешние либы. Ну и когда их компилишь. А внутри программы допустима полная анархия. Тот же clang умеет делать link time optimization, например. А в старом паскале, насколько помню, был обратный по сравнению с С порядок параметров, изза чего были проблемы с передачей переменного числа аргументов. При этом вызовы в библиотеки он дергал по тому abi, который нужен этим библиотекам, разумеется. Во-вторых компилятор может добавлять адаптеры в нужное abi. Т.е., у функции получается несколько точек входа. gcc, вероятно так не делает, но я когда-то эксперементировал с каким-то компилятором под arm, и он такое вытворял.
А стек больше не делают не потому что нет ресурсов. Просто больше ненужно. Где-то на хабре писали. что тот же apache преспокойно работает со стеком 32кб. И все равно кэшируется он кусками по сколько-то (64) байт. И все равно при вызове функции адрес возврата пишется в стек — значит он все равно будет закэширован. Но это конечно рассуждение на уровне «мне так кажется». Я не знаю как это корректно померять.
«Как вы результат компиляции собиратесь из нескольких модулей собирать, если вы в результате оптимизации испортили конвенцию о вызовах?»
Во-первых, конвенцию о вызовах надо соблюдать только когда зовешь внешние либы. Ну и когда их компилишь. А внутри программы допустима полная анархия. Тот же clang умеет делать link time optimization, например. А в старом паскале, насколько помню, был обратный по сравнению с С порядок параметров, изза чего были проблемы с передачей переменного числа аргументов. При этом вызовы в библиотеки он дергал по тому abi, который нужен этим библиотекам, разумеется. Во-вторых компилятор может добавлять адаптеры в нужное abi. Т.е., у функции получается несколько точек входа. gcc, вероятно так не делает, но я когда-то эксперементировал с каким-то компилятором под arm, и он такое вытворял.
А стек больше не делают не потому что нет ресурсов. Просто больше ненужно. Где-то на хабре писали. что тот же apache преспокойно работает со стеком 32кб. И все равно кэшируется он кусками по сколько-то (64) байт. И все равно при вызове функции адрес возврата пишется в стек — значит он все равно будет закэширован. Но это конечно рассуждение на уровне «мне так кажется». Я не знаю как это корректно померять.
Вообще-то, компилятор должен начать использовать стек только если не хватает регистров, или если в функции берется адрес переменной.Вы вообще не с той стороны смотрите. Проблема не в вызываемой функции, а в вызывающей. При описанном подходе все переменные чинно приходят через регистры (если использовать
regparm
… и да, ядро его использует) и значение возвращается тоже через регистр. А вот если пытаться вернуть два значения отдельно, то в вызывающей функции вам так или иначе нужно будет положить одну из переменных на стек — со всеми вытекающими. Не забудьте про то, что у ядра стек — всего-то 4K на всех. Дополнительная память, подо всё это задействованная, тоже ляжет на кеш. Причём тут есть ещё вот какая особенность: ядро ведь использует тот же самый кеш, что и программы (у процессора он всего один), но работает-то оно куда меньше (если у вас вдруг ядро грузит систему на 99%, то у вас случилась паталогия и вам уже ничего не поможет), так что каждый байт, который оно оттуда вытесняет куда весомее, чем в случае обычных программ (концепция микроядра на этом погорела).Можно использовать
errno
в TLS, конечно, но эта концепция появилась лет через десять после описываемого трюка. И она всё равно решает только часть проблем: обращение к памяти пусть даже в L1 — это 4 такта, к регистру — 1 такт.Ясно. Все понятно кроме одного — почему в вызывающую функцию нельзя возвратить два значения в двух регистрах, без использования стека? зачем одну из переменных понадобится класть в стек?
Потому что возврата двух переменных ABI не предусматривает, а свой компилятор разработчики Linux'а почему-то писать не захотели. На двух регистрах
long long
и возвращаются, в принципе можно было указатель и код ошибки запаковать в long long
, но с этим проблем почти столько же, сколько с описанным в статье трюком, а эффективность и переносимость ничуть не выше.Потому что возврата двух переменных ABI не предусматривает
Можно возвращать структуры, структура из двух полей трактуется почти как две переменные.
Есть, просто его назвали «Передача параметра по ссылке».
При достаточном уровне оптимизации происходит так:
«The compiler ignores any register definitions and allocates registers to variables and temporary values by using an algorithm that makes the most efficient use of registers.»
это из ARM Optimizing C/C++ Compiler v5.0 от ti
Эксперимент повторять лень, но суть в том, что ABI должно выполняться только при внешних вызовах. Вызовы внутри можно оптимизировать, и компилятор это делает, если его попросить.
При достаточном уровне оптимизации происходит так:
«The compiler ignores any register definitions and allocates registers to variables and temporary values by using an algorithm that makes the most efficient use of registers.»
это из ARM Optimizing C/C++ Compiler v5.0 от ti
Эксперимент повторять лень, но суть в том, что ABI должно выполняться только при внешних вызовах. Вызовы внутри можно оптимизировать, и компилятор это делает, если его попросить.
Прочитал камент про то, что в С нет ссылок.
Всегда компилил g++ и не задумывался про это.
Получается, что да.
Есть у меня еще 1 идея — надо будет ее попробовать. Оптимизирует ли компилятор обращение через указатель на локальную переменную. Я как попробую — отпишу, если будет положительный результат.
Пока тест не провел — соглашусь с вами. Считаем, что так сделать не получится.
Всегда компилил g++ и не задумывался про это.
Получается, что да.
Есть у меня еще 1 идея — надо будет ее попробовать. Оптимизирует ли компилятор обращение через указатель на локальную переменную. Я как попробую — отпишу, если будет положительный результат.
Пока тест не провел — соглашусь с вами. Считаем, что так сделать не получится.
почему в вызывающую функцию нельзя возвратить два значения в двух регистрах
Можно, но в С это сопряжено со следующими проблемами:
— возвращаемое значение должно быть либо структурой, либо типом данных длиннее указателя (а такого типа может не быть => структура — единственный вариант).
— структура должна либо содержать void * как поле, соответствующее указателю, либо нам нужно по одной структуре на каждый тип, указатель на который мы хотим возвращать. void * чреват ошибками, когда ожидали один тип, а вернули другой, или поменяли функцию, но не всех кто её вызывает. Второй вариант в принципе можно реализовать на макросах, но он будет многословным.
Вы вопрос вы рассмотрели вырвано из контекста.
Когда-то ковырял какой-то компилятор от техас инструментс, который вполне нормально справлялся с такой задачей — передаешь по ссылке две переменные, они ложатся в разные регистры, внутри функции эти регистры меняются определенным образом, а после возврата из функции используются. То есть в полной мере — возврат из функции двух значений через регистры. Так делал он при ненулевом уровне оптимизации. При нулевой он все данные гонял через стек. Правда ковырял его в режиме EABI, а не ABI.
Когда-то ковырял какой-то компилятор от техас инструментс, который вполне нормально справлялся с такой задачей — передаешь по ссылке две переменные, они ложатся в разные регистры, внутри функции эти регистры меняются определенным образом, а после возврата из функции используются. То есть в полной мере — возврат из функции двух значений через регистры. Так делал он при ненулевом уровне оптимизации. При нулевой он все данные гонял через стек. Правда ковырял его в режиме EABI, а не ABI.
Sign up to leave a comment.
Об одном трюке для возврата кода ошибки из функции