Не указывать указатели на C могут вызвать проблемы?
Вчера я был в классе, и в какой-то момент инструктор говорил о коде C. Он сказал:
Какова цель создания указателя в C? Единственная цель заключается в том, чтобы заставить компилятор правильно интерпретировать операции указателя (например, добавление указателя int приведет к другому смещению чем добавление указателя char). Кроме того, нет никакой разницы: все указатели представлены так же, как в памяти, независимо, если указатель указывает на целочисленное значение, значение char, короткое значение, или любой другой. Так, литье указатель ничего не изменит в памяти, это только поможет программисту с операциями более связанными с указателем типа он имеет дело с.
Однако, я прочитал, особенно здесь, в Stack Overflow, что это не 100% правда. Я прочитал, что в некоторых странных машинах указатели для разных типов могут храниться по-разному в памяти. В этом случае не изменение указателя на правильный тип может вызвать проблемы, если код скомпилирован для этого типа машины.
В принципе, это тот код, о котором я говорю. Рассмотрим следующий код:
int* int_pointer;
char* char_pointer;
int_pointer = malloc(sizeof(int));
*int_pointer = 4;
И теперь два варианта:
1.
char_pointer = (char *)int_pointer;
2.
char_pointer = int_pointer;
Код на примере 2 может стать проблемой? Создание каста (case 1) в конечном итоге изменит формат указателя в памяти (если да, не могли бы вы привести пример машины?)?
Спасибо
Ответы
Ответ 1
Что сказал ваш инструктор обо всех типах указателей, разделяющих одно и то же представление, как правило, верно для реальных реализаций языка C.
Однако это не так с точки зрения абстрактного языка C. Язык C гарантирует, что
-
char *
указатели представлены так же, как указатели void *
.
- Указатели на квалифицированную версию типа
T
(const
, volatile
, restrict
) представлены так же, как указатели на неквалифицированные T
.
- Указатели на все типы структур представлены одинаково.
- Указатели на все типы объединения представлены одинаково.
Вот и все. Никаких других гарантий не существует. Это означает, что по отношению к самому языку указатель int *
имеет другое представление из указателя double *
. А указатель на struct S
представляется иначе, чем указатель на union U
.
Однако ваш пример с char_pointer
и int_pointer
, как представлено, не совсем иллюстративен. Назначение char_pointer = int_pointer;
просто неверно (как в "не компилируется" ). Язык не поддерживает неявные преобразования между несовместимыми типами указателей. Для таких преобразований всегда требуется явный оператор литья.
Ответ 2
Не указывать указатели на C не могут?
Не существует неявного преобразования между типами указателей в C (как, например, между арифметическими типами) - с исключением с типом void *
. Это означает, что C требует, чтобы вы выбрали, если хотите преобразовать указатель в другой тип указателя (кроме void *
). Если вы этого не сделаете, для выдачи диагностического сообщения требуется выполнение, и ему разрешено выполнить перевод.
Некоторые компиляторы достаточно хороши (или достаточно извращены), чтобы не требовать приведения. Они обычно ведут себя так, как будто вы явно помещаете актерский состав.
char *p = NULL;
int *q = NULL;
p = q; // not valid in C
В заключение вы должны всегда ставить актеры при преобразовании указателей в указатели по причинам мобильности.
EDIT:
Внешняя переносимость, пример, когда не литье может вызвать проблемы, например, с помощью вариационных функций. Пусть предполагается реализация, размер которой char *
больше размера int *
. Пусть говорят, что функция ожидает, что тип одного аргумента будет char *
. Если вы хотите передать аргумент int *
, вы должны отправить его на char *
. Если вы его не произнесете, когда функция получит доступ к объекту char *
, некоторые биты объекта будут иметь неопределенное значение, а bevahior будет undefined.
Несколько близкий пример с printf
, если пользователь не может передать в void *
аргумент для спецификатора преобразования p
, C говорит, что он вызывает поведение undefined.
Ответ 3
То, что сказал ваш профессор, звучит правильно.
... (например, добавление указателя int приведет к другому смещению, чем добавление указателя char)
Это верно, потому что если int
составляет 4 байта, а char
- 1 байт, добавление 2
к указателю int приведет к перемещению указателя на 8 байтов, тогда как добавление 2
в char указатель перемещает указатель только на следующий байт на 2 позиции.
Кроме того, нет никакой разницы: все указатели представлены одинаково в памяти, независимо от того, указывает ли указатель на значение int, значение char, короткое значение или что-то еще.
Это верно, машина не делает различия между char vs int указателями, это проблема компилятора, с которой приходится иметь дело. Также, как сказал ваш профессор:
Единственная цель - заставить компилятор правильно интерпретировать операции указателя
Итак, кастинг указателя не изменит ничего в памяти, он будет просто помогите программисту с операциями, более связанными с тип указателя, с которым он имеет дело.
Это снова верно. Кастинг не изменяет память, он просто меняет порядок интерпретации части памяти.
Ответ 4
Приведение указателя не изменяет значение указателя, оно сообщает компилятору, что делать с указателем. Сам указатель - это просто число, и у него нет типа. Это переменная, в которой хранится указатель, который имеет тип.
Нет никакой разницы между двумя вашими способами назначения указателя (если компилятор позволяет вам выполнить настройку). Независимо от типа переменной указателя источника указатель будет использоваться в соответствии с типом целевой переменной после назначения.
Вы просто не можете сохранить указатель int в переменной указателя char. Как только значение сохраняется в переменной, оно не имеет представления о том, что он был указателем на int.
Указатели каста имеют значение, когда вы используете значение напрямую, например:
int* int_pointer;
int_pointer = malloc(sizeof(int));
*int_pointer = 4;
char c;
c = *(char*)int_pointer;
Приведение указателя означает, что разыменование его читает char из местоположения, а не int.
Ответ 5
Мне кажется, что использование метода void * для типа, которое мне нужно в моей программе (например, int *, char *, struct x *). Но переключение между другими типами, то есть int * на char * и т.д., Может стать проблематичным (из-за того, что смещения различаются для ints, chars и т.д.). Поэтому будьте осторожны при этом.
Но... чтобы конкретно ответить на ваш вопрос, рассмотрите следующее:
int_pointer = malloc(2 * sizeof(int)); // 2 * 4 bytes on my system.
*int_pointer = 44;
*(int_pointer + 1 )= 55;
printf("Value at int_pointer = %d\n", *int_pointer); // 44
printf("Value at int_pointer + 1 = %d\n", *(int_pointer + 1)); // 55
(смещения выше будут с шагом в 4 байта.)
//char_pointer = int_pointer; // Gives a warning ...
char_pointer = (char*)int_pointer; // No warning.
printf("Value at char_pointer = %d\n", *char_pointer); // 0
printf("Value at char_pointer = %d\n", *(char_pointer+1)); // 0
printf("Value at char_pointer = %d\n", *(char_pointer+2)); // 0
printf("Value at char_pointer = %d\n", *(char_pointer+3)); // 44 <==
Четыре байта были выделены для значения int 44. Двоичное представление 44 равно 00101100... остальные 3 байта - все 0s. Поэтому при доступе с помощью указателя char * мы увеличиваем 1 байт за раз и получаем приведенные выше значения.
Ответ 6
Кастинг для указателей необходим, чтобы сказать компилятору, как выполнить арифметику указателя.
Например, если x является указателем на символ, указывающим на память 2001, x ++ должен перейти в 2002, но если x является целым указателем x ++, то должен быть 2003
поэтому, если тип casting не выполняется для указателей, компилятор не сможет правильно выполнить арифметику указателя.