Арифметика указателя для пустого указателя в C
Когда указатель на определенный тип (скажем, int
, char
, float
,..) увеличивается, его значение увеличивается на размер этого типа данных. Если void
указатель, который указывает на данные размера x
увеличивается, как он попадает в точку x
байты впереди? Как компилятор знает, как добавить x
значению указателя?
10 ответов
Окончательный вывод: арифметика на 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*
,
Примечание редактора: этот ответ был отредактирован, чтобы отразить окончательный вывод.
Приведите его к указателю на символ, увеличивая указатель вперед на х байт вперед.
Стандарт C не допускает арифметику пустых указателей. Тем не менее, GNU C допускается с учетом размера пустоты 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;
}
Может быть другие компиляторы генерируют ошибку.
Вы не можете сделать арифметику указателя на void *
типы, именно по этой причине!
Пустые указатели могут указывать на любой фрагмент памяти. Следовательно, компилятор не знает, сколько байтов будет увеличиваться / уменьшаться, когда мы пытаемся выполнить арифметику с указателем на пустой указатель. Следовательно, указатели типа 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.
Вы должны привести его к другому типу указателя, прежде чем выполнять арифметику указателя.
[ответ скопирован из комментария к более позднему дублирующему вопросу ]
Разрешение арифметических операций с пустыми указателями является спорным и нестандартным расширением. Если вы думаете на языке ассемблера, где указатели — это просто адреса, арифметика с пустыми указателями имеет смысл, и добавление 1 просто добавляет 1. Но если вы думаете в терминах C, используя модель арифметики указателей C, добавляя 1 указательp
на самом деле добавляетsizeof(*p)
по адресу, и это то, что вы хотите, чтобы арифметика указателя делала, но посколькуsizeof(void)
равен 0, он разбивается на пустые указатели.
Если вы мыслите в терминах C, вы не возражаете, что он ломается, и вы не возражаете против вставки явных приведений к(char *)
если это арифметика, которую вы хотите. Но если вы думаете на ассемблере, вы хотите, чтобы он просто работал, поэтому расширение (хотя и отклонение от правильного определения арифметики указателей в C) желательно в некоторых кругах и предоставляется некоторыми компиляторами.
В пустом указателе не допускается арифметика указателя.
Причина : арифметика указателя отличается от обычной арифметики, поскольку выполняется относительно базового адреса .
Решение : используйте оператор приведения типа во время арифметики, это сделает базовый тип данных известным для выражения, выполняющего арифметические действия с указателем. пример: точка - это указатель на пустоту
*point=*point +1; //Not valid
*(int *)point= *(int *)point +1; //valid
Компилятор знает тип приведения. Учитывая 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)