Подтвердить что ты не робот

Не указывать указатели на 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) в конечном итоге изменит формат указателя в памяти (если да, не могли бы вы привести пример машины?)?

Спасибо

4b9b3361

Ответ 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 не выполняется для указателей, компилятор не сможет правильно выполнить арифметику указателя.