2013-05-19 1 views
3

меня на структуру следующим образом:Создание пустоту * в пределах точки STRUCT до целого

typedef struct Node { 
    void* data; 
    unsigned long id; 
    NodePtr next; 
    NodePtr prev; 
} Node; 

Это означает, что узел в связанном списке ADT. У меня есть два разных конструктора в зависимости от того, что Узел должен хранить в данных. Один конструктор использует:

NodePtr TempNode; 
TempNode = malloc(sizeof(Node)); 
/* Other stuff */ 
TempNode->data = newList(); 
return (TempNode); 

И это, кажется, работает очень хорошо для давая мне доступ к этому списку, возвратив (list-> current-> данные), где ток является указателем узла в списке Struct

Однако , Я хочу сделать версию конструктора, где (данные) указывает на int. Я прочитал, что я могу это сделать, выполнив следующие действия

void* ptr; 
int x = 0; 
*((int*)ptr) = x; 

Но с тем, как мой конструктор настроен, это означало бы, что я должен сделать что-то вроде этого?

*((int*)TempNode->data) = 1; // data starts at 1 

И это не сработает. Я очень новичок в C, поэтому я не очень понимаю терминологию. Я прочитал, что разыменование (с использованием знака ->?) Не может быть выполнено с помощью void *, но, похоже, он отлично работает в моей версии конструктора. Как я могу переписать мой другой конструктор, чтобы передать эту void * в int?

+1

Указатель * нуждается в *, чтобы указать на определенное хранилище, которое вам разрешено использовать. Он может быть автоматическим, статическим, угрожающим, локальным или динамическим, но вы * не можете * использовать любой бит памяти, на который это указывает! – dmckee

+0

Но разве это не значит, что мои вызовы в List-> current-> data не должны возвращать список для случая, когда были установлены данные = newList()? Похоже, они это делают. –

+0

Нет, потому что в этом случае вы * установите * 'data'. В другом случае вы просто принимаете какое бы то ни было значение в памяти, которая была передана вам за ваш «узел» (который может быть буквально * любым значением, выражаемым в 'sizeof (void *)' bytes). – dmckee

ответ

1

Давайте поговорим об этом

Когда вы делаете что-то вроде

NodePtr TempNode; 
TempNode = malloc(sizeof(Node)); 

вы попросили библиотеку reseve вам некоторую динамическую память, которая является достаточно большой для Node. Начальные значения этой памяти не определены, поэтому прямо сейчас указатель TempNode->data может указывать в любом месте и, вероятно, не указывать на память, зарезервированную для вашего использования.

Когда вы

TempNode->data = newList(); 

вы даете указатель (предположительно, до тех пор, как newList() делает что-то юридическое и разумное) действительное значение;

Если вместо этого вы

*((int*)TempNode->data) = 1; 

вы проинструктировать компилятор

  1. лакомство TempNode->Data как межд указатель на,
  2. де-ссылки его и
  3. установить значение от того, что каждая память находится на другом конце до 1 (обратите внимание, что ни при каких обстоятельствах вы не установили data, at)

Но вы не знаете, на что это указывает! Отказ от ссылки - это неопределенное поведение и, строго говоря, плохое.

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

+0

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

+0

Локальная переменная, глобальная переменная, динамические распределения (возвращаемые значения из семейства функций 'alloc') - это все биты, которые вы можете использовать (по крайней мере, до тех пор, пока локальные переменные не выйдут из области действия или не будут' free() 'динамические распределения). – dmckee

2

я настоятельно совет против делать это, но если вы действительно хотите использовать void * элемент провести целое число, вы можете сделать:

Node *constructor_int(int n) 
{ 
    Node *tmp = malloc(sizeof(*tmp)); 
    /* Other stuff */ 
    tmp->data = (void *)n; 
    return(tmp); 
} 

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

Очевидный, логический способ сделать это состоит в выделении целое для data члена, чтобы указать на:

Node *constructor_int(int n) 
{ 
    Node *tmp = malloc(sizeof(*tmp)); 
    /* Other stuff */ 
    tmp->data = malloc(sizeof(int)); 
    *(int *)temp->data = n; 
    return(tmp); 
} 

Вы просто должны помнить, чтобы освободить два распределения памяти.

Код также должен проверить, что распределения памяти преуспели перед использованием результатов.

+0

+1 Я ожидал увидеть, что 'intptr_t' входит в этот ответ где-то, но чем больше я думал об этом, тем лучше ответ« сделай это правильно »- это двойное замыкание. Может возникнуть соблазн выйти замуж за два в один адрес (sizeof (int) + sizeof (* tmp)) и установить относительные смещения, но есть платформы, где это вызовет ошибки выравнивания (не так много, но некоторые). Хороший ответ. – WhozCraig

+0

Спасибо. Означает ли это, что теперь я могу обрабатывать данные NodePtr-> как int? и в других областях вызова кода List-> current-> data ++? –

+0

Вы должны рассматривать его как 'void *', который может быть законно передан 'int *' и разыменован. Следовательно, '(* (int *) ModePtr-> data) ++' и '(* (int *) List-> current-> data) ++' будет в порядке. Я бы, вероятно, работал, чтобы избежать необходимости делать это очень часто - я бы посмотрел на дизайн всей системы и посмотрел, есть ли лучший способ сделать что-то - возможно, даже отойти от одного ADT, чтобы использовать один ADT для целочисленный список и другой ADT для тех, которые используются с 'newList()' (где абстрактность возникает под вопросом). Если вам действительно нужны типы шаблонов, возможно, вам стоит рассмотреть C++. –

1

«Как я могу сделать точку данных в области, которую я могу использовать

Я не уверен, если вы имеете в виду то, что я объясню ниже (и это будет недолго: P), но если вы спрашиваете, как вы можете различать тип данных, хранящихся в Node->data, то с этой реализацией вы не сможете.

Вы оставляете это до конца программиста, чтобы помнить, какие данные он хранил в списке (что не так плохо, кстати, наоборот, это норма). Другими словами, вы доверяете конечному программисту, что он/она будет применять правильные отбрасывания, когда скажет печать Node->data.

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

Например ...

enum DataType { 
    DT_INVALID = 0, 
    DT_PTR 
    DT_CHAR, 
    DT_INT, 
    DT_FLOAT, 
    DT_DOUBLE, 
    ... 
    /* not a data-type, just their total count */ 
    DT_MAX 
}; 
#define DT_IS_VALID(dt) ((dt) > DT_INVALID && (dt) < DT_MAX) 

typedef struct List List; 
struct List { 
    enum DataType dt; 
    Node *head; 
}; 

Конечно вы можете поддерживать меньше или больше типов данных, чем те, которые я перечислил в enum, выше (даже пользовательские те, скажем, для строк, или что-то вы считаете нужным в соответствии с вашим проектом).

Итак, сначала вы будете нуждаться в конструкторе (или инициализаторе) для списка, что-то вроде этого ...

List *new_list(enum DataType dt) 
{ 
    List *ret = NULL; 

    if (!DT_IS_VALID(dt)) 
     return NULL; 

    ret = malloc(sizeof(List)); 
    if (NULL == ret) 
     return NULL; 

    ret->dt = dt; 
    ret->head = NULL; 

    return ret; 
} 

и создать его экземпляр, скажем так ...

int main(void) 
{ 
    List *listInt = new_list(DT_INT); 
    if (NULL == list) { 
     /* handle failure here */ 
    } 

Теперь, когда вы уже сохранили заданный тип данных в метаданных списка, вы можете свободно выбирать, как реализовать конструкторы Node. Например, общий один может выглядеть примерно так ...

int list_add_node(List *list, const void *data) 
{ 
    Node *node = NULL; 
    size_t datasz = 0; 

    /* sanity checks */ 
    if (!list || !data || !DT_IS_VALID(list->dt)) 
     return 0; /* false */ 

    node = malloc(sizeof(Node)); 
    if (NULL == node) 
     return 0; /* false */ 

    /* when data points to mem already reserved say for an array (TRICKY) */ 
    if (DT_PTR == list->dt) { 
     node->data = data; 
    } 
    /* when data points to mem reserved for a primitive data-type */ 
    else { 
     datasz = dt_size(list->dt); /* implement dt_size() according to your supported data-types */ 
     node->data = malloc(datasz); 
     if (NULL == node->data) { 
      free(node); 
      return 0; /* false */ 
     } 
     memcpy(node->data, data, datasz); 
    } 

    /* add here the code dealing with adding node into list->head */ 
    ... 
    return 1; /* true */ 
} 

Для DT_PTR (который я помечен как Tricky в данном примере), что является более безопасным для реализации другого Node конструктора, возможно, принимая 2 дополнительные аргументы, давайте скажем elemsz и nelems, поэтому функция может выделить elemsz * nelems байтов для копирования содержимого data в них, в случае, если data указывает на массив, структуру или любой другой непримитивный тип.Или вы можете предоставить дополнительные значения DT_ARR enum специально для массивов. Вы можете делать все, что вам подходит.

В любом случае, для DT_PTR приведенный выше пример зависит от вызвавшей list_add_node, чтобы должным образом выделены переданную data, и в общем контексте это не хорошая вещь вообще.

Код более сложный, но вы знаете тип данных, хранящийся в вашем списке. Поэтому, по крайней мере, для примитивных типов данных, которые вы можете добавить, можно сказать, что процедура печати автоматически выдает свой вывод в соответствии с list->dt (для не-примитивных типов данных вы должны обеспечивать поддержку пользовательских процедур печати, обычно через функции обратного вызова).

Вы даже можете дотянуться до крайности, и переместите поле dt от List до Node. В этом случае вы реализуете список гетерогенных данных в узлах, но он становится намного сложнее, а также редко бывает полезным (если вообще когда-либо).

Все эти предметы ADT с помощью (void *) указателей имеют серьезные проблемы с производительностью, поэтому критические реализации скорости используют вместо этого предварительный процессор (или злоупотребляют, если хотите), для такого рода вещей.

+0

Ничего себе, спасибо за подробное объяснение. Я знаю, что мой способ сделать это не является оптимальным, и оставляет место для ошибок. К счастью, похоже, что объяснение Джонатана Леффлера, похоже, работает для моих целей. Когда я получаю шанс, я сяду и посмотрю на ваш пост, чтобы я действительно мог понять, что происходит. Еще раз спасибо! –

+0

Добро пожаловать (кстати, я только что исправил некоторые опечатки, может быть и больше). Поскольку ответ Джонатана был самым полезным, подумайте о том, чтобы принять его, когда ваш представитель немного выше;) –

 Смежные вопросы

  • Нет связанных вопросов^_^