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

Кастинг указателя - В чем разница во время выполнения?

Рассмотрим следующий небольшой пример кода:

#include <stdio.h>
#include <stdlib.h>

int main()
{
    int *i;
    char *c1, *c2;

    i = malloc(4);

    *i = 65535;

    c1 = i;
    c2 = (char *)i;

    printf("%p %p %p\n", i, c1, c2);
    printf("%d %d", *c1, *c2);

    free(i);

    return 0;
}

В этом примере я выделяю память для хранения целого числа, на которое указывает i. Затем я сохраняю значение 65535 (1111 1111 1111 1111) в *i. Следующее, что я делаю, это сделать два указателя char *, также указывая на целое число. Я делаю это два раза, но двумя разными способами: c1 = i; и c2 = (char *)i;. Наконец, я печатаю все указатели и все значения на экране. Три указателя указывают на один и тот же адрес, и два значения *c1 и *c2 являются правильными (- 1).

Однако компилятор генерирует предупреждение в этой строке: c1 = i;. Предупреждение генерируется, потому что я не использовал приведение (char *) для выполнения задания.

То, что я хотел бы задать, - почему, компилятор генерирует это предупреждение, так как я не вижу любую разницу при использовании c1 = i; или c2 = (char *)i;. В обоих случаях результатом является тот же адрес с одинаковым размером в байтах. И это справедливо для всех бросков, даже если это (int *) cast, (float *) cast, (short *) cast и т.д. Все они генерируют одно и то же значение, но компилятор принимает его только без предупреждения, если Используемый тип используется для типа указателя.

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

4b9b3361

Ответ 1

При использовании:

c2 = i;

компилятор предупреждает вас о назначении типа int* на char*. Это потенциально может быть непреднамеренной ошибкой. Компилятор предупреждает вас, надеясь, что если это действительно непреднамеренная ошибка, у вас есть шанс исправить ее.

При использовании:

c2 = (char *)i;

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

Ответ 2

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

6.5.4 Операторы роли

3 - Преобразования, которые включают указатели, кроме того, где это разрешено ограничениями 6.5.16.1, должны быть указаны с помощью явного приведения.

Компилятор принимает ваш код как расширение, но другой компилятор может отклонить ваш код и будет правильным для этого. Вот почему компилятор выдает предупреждение при опускании трансляции.

Ответ 3

Указатели на объекты разных типов могут отличаться по своему размеру и выравниванию:

§6.2.5/26 Типы

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

В частности, sizeof(char *) может иметь другое значение, чем sizeof(int *). Кроме того, char * может иметь разные требования к выравниванию, чем int *. Следовательно, неявный бросок дает предупреждение. Вот почему стандарт C содержит следующее руководство:

§6.5.4/1

Конверсии, которые включают указатели, кроме того, где это разрешено ограничения 6.5.16.1, должны быть указаны с помощью явное приведение.

Ответ 4

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

Ответ 5

В более сложных решениях могут возникнуть проблемы, если вы этого не сделаете. Это не вызывает проблем в этом случае, но когда компилятору необходимо обрабатывать выделенную ячейку памяти по своему типу, она может ошибиться. Обратите внимание, что char имеет длину 1 байт, а int - 4 или 8 байтов.

Ответ 6

К сожалению, лучший ответ в комментариях к вопросу!

См. @PieterWitvoet commment:

В C система типов - это инструмент времени компиляции, который предотвращает выполнение недействительных операций. Вещи разных типов доступным для них различным операциям, и компилятор делает убедитесь, что вы используете только действительные операции. Но некоторые операции один тип может работать отлично для другого типа, поэтому, когда вы хотите использовать те, которые вы можете выполнить для типа-cast, который в основном сообщает компилятору: "Я знаю, что я делаю, просто притворяйся, что эта вещь здесь тип, действительный для этой операции".

Если вы все еще не понимаете: C - статически типизированный язык. Это означает, что программист должен определить тип идентификаторов перед их использованием. Таким образом, одной из целей компилятора C является обеспечение того, чтобы вы присвоили тип всем идентификаторам. Другая цель - обеспечить, чтобы назначенные значения имели тот же тип, что и идентификатор, которому они назначены. В случае указателей они внутренне одного типа i.e.., они 32-битные на 32-битных системах. Однако аспект высокого уровня указателей заключается в том, что они имеют указатели на определенный "тип" данных. Таким образом, имеет смысл гарантировать, что указателю присваивается адрес данных, который имеет ожидаемый тип. Я смутно помню, что такой чек изначально не существовал и был добавлен в более поздний выпуск стандарта C. Однако я мог ошибаться. Если такая проверка не выполняется, тогда можно было бы сохранить адрес неправильных данных, и это будет реализовано как ошибка времени выполнения. Например, присваивание адреса структуры int/ char/любому другому типу, а затем косвенное отношение к тому же, предполагая, что он относится к соответствующему типу структуры, может привести к непредвиденным проблемам во время выполнения. Дело не в том, что такие присвоения недействительны, поэтому компилятор предупреждает вас, и если вы набираете значение, то компилятор не будет беспокоиться об этом, потому что тогда он понимает, что вы знаете, что вы делаете.