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

Типизация указателей в C

Я знаю, что указатель на один тип может быть преобразован в указатель другого типа. У меня три вопроса:

  • Что следует учитывать при указателях при указании типов?
  • Каковы исключения/ошибка в результирующем указателе?
  • Что лучше всего избегать исключений/ошибок?
4b9b3361

Ответ 1

Программа, хорошо написанная, обычно не использует многозначное приведение типов. Может потребоваться использовать ptr typecast для malloc, например (объявлено (void *)malloc(...)), но это даже не нужно в C (в то время как несколько компиляторов могут жаловаться).

  int *p = malloc(sizeof(int)); // no need of (int *)malloc(...)

Однако в системных приложениях иногда вы хотите использовать трюк для выполнения двоичной или конкретной операции - и C, язык, близкий к структуре машины, для этого удобен. Например, вы хотите проанализировать двоичную структуру двойника (которая следует за реализацией IEEE 754), а работа с бинарными элементами проще, вы можете объявить

  typedef unsigned char byte;
  double d = 0.9;
  byte *p = (byte *)&d;
  int i;
  for (i=0 ; i<sizeof(double) ; i++) { ... work with b ... }

Вы также можете использовать объединение, это пример.

Более сложным использованием может быть симуляция С++ полиморфизма, которая требует хранения иерархии классов (структур) где-то запоминать что и что, и выполнить приведение типов указателей, чтобы, например, родительская переменная-указатель класса, указывать в какой-то момент на производный класс (см. также ссылку на С++)

  CRectangle rect;
  CPolygon *p = (CPolygon *)&rect;
  p->whatami = POLY_RECTANGLE; // a way to simulate polymorphism ...
  process_poly ( p );

Но в этом случае, возможно, лучше использовать С++!

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

Потенциальные потенциальные опасности указателя

  • использовать их, когда это не нужно - это ошибка, подверженная ошибкам и усложняющая программу
  • указывающий на объект разного размера, который может привести к переполнению доступа, неправильному результату...
  • указатель на две разные структуры, такие как s1 *p = (s1 *)&s2;: полагаясь на их размер и выравнивание, может привести к ошибке

(Но, честно говоря, опытный программист С не допустил бы совершения вышеуказанных ошибок...)

Лучшая практика

  • используйте их только в том случае, если они вам понадобятся, и прокомментируйте часть, которая объясняет, почему это необходимо.
  • знайте, что вы делаете - снова опытный программист может без проблем использовать тонны типов указателей, т.е. не попробовать и не видеть, он может работать на такой системе/версии/ОС и может не работать на другой

Ответ 2

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

Вот пример кода указателей структуры заливки:

struct Entity { 
  int type;
}

struct DetailedEntity1 {
  int type;
  short val1;
}

struct DetailedEntity2 {
  int type;
  long val;
  long val2;
}

// random code:
struct Entity* ent = (struct Entity*)ptr;

//bad:
struct DetailedEntity1* ent1 = (struct DetailedEntity1*)ent;
int a = ent->val; // may be an error here, invalid read
ent->val = 117; // possible invali write

//OK:
if (ent->type == DETAILED_ENTITY_1) {
  ((struct DetailedEntity1*)ent)->val1;
} else if (ent->type == DETAILED_ENTITY_2) {
  ((struct DetailedEntity2*)ent)->val2;
} 

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

При кастовании с указателем на указатель (структура или нет) вы должны убедиться, что память aligned точно так же. При отливке целых структур лучший способ гарантировать, что он должен использовать тот же порядок тех же переменных в начале и дифференцировать структуры только после "общего заголовка". Также помните, что выравнивание памяти может отличаться от машины к машине, поэтому вы не можете просто отправить указатель структуры в виде байтового массива и получить его как массив байтов. Вы можете столкнуться с неожиданным поведением или даже segfaults.

При отбрасывании указателей меньших по размеру переменных вы должны быть очень осторожны. Рассмотрим этот код:

char* ptr = malloc (16);
ptr++;
uint64_t* uintPtr = ptr; // may cause an error, memory is not properly aligned

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

Ответ 3

Вам, вероятно, нужно взглянуть на... C-faq, поддерживаемый Steve Summit (который использовался для быть размещенными в группах новостей, что означает, что он был прочитан и обновлен многими лучшими программистами в то время, иногда самими разработчиками самого языка).

Существует сокращенная версия, которая, возможно, более приемлема и все еще очень, очень, очень, очень полезно. Чтение всего сокращенного является, я считаю, обязательным, если вы используете C.