Арифметика указателя для пустого указателя в C

177

Когда указатель к определенному типу (скажем int, char, float, ..) увеличивается, его значение увеличивается на размер этого типа данных. Если voidуказатель, который указывает на данные размера x, увеличивается, как он может указывать xвперед байты? Как компилятор знает, как добавить xзначение указателя?

Шива Шанкаран
источник
3
Вопрос звучит так, как будто он предполагает, что компилятор (/ run-time) знает, на какой тип объекта был установлен указатель, и добавляет его размер к указателю. Это полное заблуждение: он знает только адрес.
PJTraill
2
«Если voidуказатель, который указывает на данные размера x, увеличивается, как он может указывать xбайты вперед?» Это не так. Почему люди, у которых есть такие вопросы, не могут их проверить, прежде чем спросить - вы знаете, по крайней мере, до минимума, когда они проверяют, действительно ли он компилируется, а это не так. -1, не могу поверить, что получил +100 и -0.
underscore_d

Ответы:

287

Окончательный вывод: арифметика на void*это незаконно и в C и C ++.

GCC допускает это как расширение, см. Арифметические voidи функциональные указатели (обратите внимание, что этот раздел является частью главы «Расширения C» в руководстве). Clang и ICC, вероятно, разрешают void*арифметику в целях совместимости с GCC. Другие компиляторы (такие как MSVC) не разрешают арифметику void*, и GCC запрещает ее, если -pedantic-errorsуказан флаг или -Werror-pointer-arithуказан флаг (этот флаг полезен, если ваша кодовая база также должна компилироваться с MSVC).

Стандарт C говорит

Цитаты взяты из проекта N1256.

Стандартное описание операции сложения гласит:

6.5.6-2: Кроме того, либо оба операнда должны иметь арифметический тип, либо один операнд должен быть указателем на тип объекта, а другой - целочисленным.

Таким образом, вопрос здесь заключается в том, является ли void*указатель на «тип объекта» или, что эквивалентно, voidявляется ли «тип объекта». Определение типа объекта:

6.2.5.1: Типы делятся на типы объектов (типы, которые полностью описывают объекты), типы функций (типы, которые описывают функции) и неполные типы (типы, которые описывают объекты, но не имеют информации, необходимой для определения их размеров).

И стандарт определяет voidкак:

6.2.5-19: voidтип содержит пустой набор значений; это неполный тип, который не может быть завершен.

Поскольку voidэто неполный тип, это не тип объекта. Поэтому он не является допустимым операндом для операции сложения.

Поэтому вы не можете выполнять арифметику указателя на voidуказателе.

Ноты

Первоначально считалось, что void*арифметика была разрешена из-за этих разделов стандарта C:

6.2.5-27: указатель на void должен иметь те же требования к представлению и выравниванию, что и указатель на тип символа.

Тем не мение,

Те же требования к представлению и выравниванию подразумевают взаимозаменяемость в качестве аргументов функций, возвращаемых значений функций и членов объединений.

Таким образом, это означает, что printf("%s", x)имеет то же значение, xимеет ли тип char*или void*, но это не означает, что вы можете делать арифметику на void*.

Примечание редактора: этот ответ был отредактирован, чтобы отразить окончательный вывод.

Садек
источник
7
Из стандарта C99: (6.5.6.2) Кроме того, либо оба операнда должны иметь арифметический тип, либо один операнд должен быть указателем на тип объекта, а другой - целочисленным. (6.2.5.19) Тип void содержит пустой набор значений; это неполный тип, который не может быть завершен. Я думаю, это ясно дает понять, что void*арифметика указателей не допускается. GCC имеет расширение, которое позволяет это делать.
Работа
1
если вы больше не считаете свой ответ полезным, вы можете просто удалить его.
кафе
1
Этот ответ был полезен даже при том, что он оказался неправильным, поскольку он содержит убедительное доказательство того, что пустые указатели не предназначены для арифметики.
Бен Флинн
1
Это хороший ответ, у него есть правильное заключение и необходимые цитаты, но люди, которые пришли к этому вопросу, пришли к неправильному выводу, потому что они не читали до конца ответа. Я отредактировал это, чтобы сделать это более очевидным.
Дитрих Эпп
1
Clang и ICC не допускают void*арифметику (по крайней мере, по умолчанию).
Сергей Подобрый
61

Арифметика void*указателей на указатели не допускается .

работа
источник
16
+1 Арифметика указателей определяется только для указателей на (завершить) типы объектов . voidявляется неполным типом, который никогда не может быть завершен по определению.
Скот
1
@schot: Точно. Кроме того, арифметика указателей определяется только для указателя на элемент объекта массива и только в том случае, если результатом операции будет указатель на элемент в этом же массиве или один после последнего элемента этого массива. Если эти условия не выполняются, это неопределенное поведение. (Из стандарта C99 6.5.6.8)
Работа
1
Видимо, это не так с gcc 7.3.0. Компилятор принимает p + 1024, где p void *. И результат такой же, как ((char *) p) + 1024
zzz777
@ zzz777 это расширение GCC, см. ссылку в ответе сверху.
Руслан
19

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

Ultimate Gobblement
источник
4
Зачем беспокоиться? Просто приведите его к нужному типу - который всегда должен быть известен - и увеличьте его на 1. Приведение к char, увеличение на x, а затем переосмысление нового значения, поскольку некоторый другой тип является бессмысленным и неопределенным поведением.
underscore_d
9
Если вы пишете свою функцию сортировки, которая в соответствии с man 3 qsortдолжна иметь void qsort(void *base, size_t nmemb, size_t size, [snip]), то у вас нет никакого способа узнать «правильный тип»
alisianoi
16

Стандарт C не допускает арифметику пустых указателей. Тем не менее, GNU C допускается с учетом размера пустот IS 1.

Стандарт С11 §6.2.5

Абзац 19

voidТипа содержит пустой набор значений; это неполный тип объекта, который не может быть завершен.

Следующая программа работает нормально в компиляторе GCC.

#include<stdio.h>

int main()
{
    int arr[2] = {1, 2};
    void *ptr = &arr;
    ptr = ptr + sizeof(int);
    printf("%d\n", *(int *)ptr);
    return 0;
}

Может быть другие компиляторы генерируют ошибку.

ЦКМ
источник
14

Вы не можете делать арифметику указателей на void *типах, именно по этой причине!

Оливер Чарльзуорт
источник
8

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

Jakob
источник
7

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

void *p = malloc(sizeof(char)*10);
p++; //compiler does how many where to pint the pointer after this increment operation

char * c = (char *)p;
c++;  // compiler will increment the c by 1, since size of char is 1 byte.
tchrist
источник
-1

Компилятор знает тип приведения. Учитывая void *x:

  • x+1добавляет один байт x, указатель переходит на байтx+1
  • (int*)x+1добавляет sizeof(int)байты, указатель переходит на байтыx + sizeof(int)
  • (float*)x+1адреса sizeof(float)байтов и т. д.

Несмотря на то, что первый элемент не переносим и не соответствует Galateo C / C ++, он, тем не менее, корректен на языке C, что означает, что он будет компилироваться во что-то на большинстве компиляторов, возможно, требующих соответствующего флага (например, -Wpointer-arith)

Rytis
источник
2
Althought the first item is not portable and is against the Galateo of C/C++Правда. it is nevertheless C-language-correctЛожь. Это двойное мышление! Арифметика указателей на void *синтаксически недопустима, не должна компилироваться и выдает неопределенное поведение, если это так. Если неосторожный программист может заставить его скомпилировать, отключив какое-то предупреждение, это не оправдание.
underscore_d
@underscore_d: Я думаю, что некоторые компиляторы использовали его как расширение, поскольку это намного удобнее, чем приведение, unsigned char*например, для добавления sizeofзначения в указатель.
суперкат