LINUX.ORG.RU

Арифметика указателей и void*

 , , ,


0

4

Допускает ли стандарт C арифметику указателей с участием void*? Нечто типа

void *buffer = getBuffer();
buffer += offset;

Насколько я понимаю, это undefined behavior, но компиляторы это пропускают и обрабатывают адекватно. Где можно про это прочитать?

Второй вопрос - пропускают ли подобный код в ядро? Если со ссылкой на прецедент, то вообще здорово.

Deleted

Последнее исправление: Deleted (всего исправлений: 1)

См. параграф стандарта 6.5.6 Additive operators

Стандарт определяет семантику только для случаев, когда указатель указывает на массив элементов типа T.

Поскольку указатель типа void * не может указывать на массив (т.к. элемент с типом void невозможен), можно сделать вывод, что семантика не определена.

Deleted
()

Как я помню, указатель на void должен вмещать в себя самый длинный из указателей целевой платформы. Т.е. для 32-битной void* будет шириной в 32 бита, для 64-битной — 64.

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

Поскольку указатель типа void * не может указывать на массив (т.к. элемент с типом void невозможен), можно сделать вывод, что семантика не определена.

не совсем так. Void* это костыль для полиморфизма в сишке. Ну например для того, что-бы реализовать malloc(3). Функция malloc(3) как раз и возвращает void*, и это вполне реальный и валидный указатель.

Я понимаю, что это плохо, но иначе никак. Подразумевается, что размер одного «элемента» void* равен 1 char'у, хотя конечно этот «элемент» фиктивный.

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

int a[10];
int* ptr = &(a[10]);// так можно
*ptr;// а так уже нельзя
ptr = &(a[11]);// так тоже нельзя, UB
emulek
()
Ответ на: комментарий от emulek

По стандарту положено ptrdiff_t для хранения разницы двух указателей. А операция вычитания определена только для операторов указывающих на один и тот же массив.

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

По стандарту положено ptrdiff_t для хранения разницы двух указателей.

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

А операция вычитания определена только для операторов указывающих на один и тот же массив.

в принципе да.

Тут вопрос к ТСу, на кой ляд ему это вычитание понадобилось-то? Пусть скастует свой void* в то, что это на самом деле, и вычитает.

например

uint8_t* buffer = getBuffer();
ptrdiff_t offset = 17;// тут можно size_t
buffer += offset;
на кой ляд делать буфер хрен знает чего?

emulek
()
Ответ на: комментарий от Deleted

Тред не читай&сразу отвечай.

если ты про

Мне не писать, мне обоснование чтобы мочить.

то основание у тебя уже есть, без арифметики, достаточно void *buffer = getBuffer(), потому что нет смысл выделять буфер, который невозможно использовать для хранения информации.

Остался вопрос про ядро.

в ядре какой-то другой стандарт?

emulek
()
Ответ на: комментарий от Deleted

Тебя не пустят в ядро с UB. Даже с подозрением на UB.

sambist ★★
()

компиляторы это пропускают

gcc у меня вот так ругается

warning: pointer of type 'void *' used in arithmetic

обрабатывают адекватно

Как понять «адекватно» в случае UB? Вот у меня перемещает указатель на offset байт.

Второй вопрос - пропускают ли подобный код в ядро?

Руки бы оторвать за такое. Больше потенциальных багов в ядре!

А главное, нахрена? Если ты сдвигаешь указатель куда-то, значит, ты знаешь, на сколько байт ты хочешь его сдвинуть. Кастани в char* и двигай себе.

grondek
()
Ответ на: комментарий от Deleted

Это не UB. Это запрещено в современном С (illegal), но некоторые компиляторы еще разрешают, с sizeof(void) == 1

anonymous
()
Ответ на: комментарий от grondek

значит, ты знаешь, на сколько байт ты хочешь его сдвинуть. Кастани в char* и двигай себе.

лучше в uint8_t*, вообще-то char не обязан быть байтом из 8и битов.

Это не UB.

это не UB, UB неизбежно будет ниже по коду.

emulek
()
Ответ на: комментарий от anonymous

это расширение gcc

Ага. Тогда повторю уже прозвучавший тут вопрос - а зачем так делать?

код такой в ядре есть

Ну надо же. Оказывается и от анонимуса польза есть.

Deleted
()

#define MAKEPTR(type,base,offs) ((type)((uintptr_t)(base)+(uintptr_t)(offs)))

anonymous
()

На практике всегда было как с char*.

anonymous
()
Ответ на: комментарий от jtootf

sizeof void в C равен sizeof char

В C он не определён. Равен sizeof char - это тоже расширение гцц.

Deleted
()
Ответ на: комментарий от anonymous

Огорчу я тебя невероятно. Арифметика с void * это расширение gcc, и код такой в ядре есть, см http://lxr.free-electrons.com/source/lib/bsearch.c

я всегда не понимал разработчиков, которые ради функции в десять строк обмазываются callback указателями. Особенно учитывая, что в большинстве случаев этот callback является обёрткой к какой-нить strcmp(3) или вообще к вычитанию двух целых чисел.

emulek
()
Ответ на: комментарий от Deleted

Ага. Тогда повторю уже прозвучавший тут вопрос

в данном случае это полиморфизм: использования массива из хрен знает чего. Ну и тут в функцию передаётся указатель на другую функцию, которая это хрен знает что сравнивает. Если совсем упарываться, то для полноты нужна ещё одна callback, что-бы переходить к следующему эл-ту коллекции. Но вот тут сэкономили немножко, передав размер хрен знает чего.

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