Использование указателей void в универсальной функции доступа с ANSI C - PullRequest
0 голосов
/ 04 сентября 2011

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

Мой пример кода выглядит следующим образом:

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

struct config_struct {
  int port;
  char *hostname;
};

typedef struct config_struct config;

void setup(config*);
void change(config*);
void set_hostname(config*, char*);
void get_hostname_into(config*, char**);
void teardown(config*);
void inspect(config*);

int main() {

  char* hostname;
  config* c;
  c = calloc( 1, sizeof(config));

  setup(c);
  inspect(c);

  change(c);
  inspect(c);

  set_hostname(c, "test.com");
  inspect(c);

  get_hostname_into(c, &hostname);
  inspect(c);  
  printf("retrieved hostname is %s (%p)\n", hostname, &hostname);

  teardown(c);
  printf("retrieved hostname is %s (%p) (after teardown)\n", hostname, &hostname);

  return EXIT_SUCCESS;
}

void setup(config* c) {
  c->port     = 9933;
  c->hostname = "localhost";
}

void change(config* c) {
  c->port     = 12345;
  c->hostname = "example.com";
}

void set_hostname(config* c, char* new_hostname) {
  c->hostname = new_hostname;
}

void get_hostname_into(config* c, char** where) {
  *where = c->hostname;
}

void teardown(config* c) {
  free(c);
}

void inspect(config* c) {
  printf("c is at %p\n", c);
  printf("c is %ld bytes\n", sizeof(*c));
  printf("c:port is %d (%p)\n", c->port, &(c->port));
  printf("c:hostname is %s (%p)\n", c->hostname, &(c->port));
}

Это требуется по природебиблиотека (функция get_session_property(session*, enum Property, void*) - таким образом, я ищу способ разыменования пустого указателя; я смог успешно реализовать это для int, но пытался понять, как это сделатьэто для char* (что-то от void* до int имеет некоторый смысл, но я не могу понять, как это сделать для void* to char*.

Моя успешная реализация (с тестами) длябиблиотека находится на моей ветке Github проекта, здесь .

Самое близкое, что я пришел, это:

enum Property { Port, Hostname };
void get_property(config*, enum Property, void*);
void get_property(config* c, enum Property p, void* target) {
  switch(p) {
    case Port:
      {
        int *port;
        port = (int *) target;
        *port = c->port;
      }
      break;
    case Hostname:
      {
        char *hostname;
        hostname = (char *) target;
        *hostname = c->hostname;
      }
      break;
  }
}

Который, к счастью, не является ошибкой, но такжеоставляет char *get_hostname_into_here null, выдавая предупреждение (которое я не могу понять:)

untitled: In function ‘get_property’:
untitled:33: warning: assignment makes integer from pointer without a cast

Полный исходный код моего надуманного примера здесь; пожалуйста, отвечая на объяснение, или порекомендуйте любое чтение, которое у вас естьпри использовании пустых указателей и / или хорошего стиля C, кажется, что каждыйу одного есть другая идея, и несколько человек, которых я знаю в реальном мире, просто сказали: «Библиотека делает это неправильно, не используйте пустые указатели) - хотя было бы хорошо, если бы библиотека делала структуру общедоступной;для инкапсуляции и по другим веским причинам, я думаю, что указатели на пустоту, универсальный подход к функциям вполне разумны в этом случае.

Итак, что я делаю неправильно в моей ветке hostname get_property()функция, что char* равен NULL после вызова get_property(c, Hostname, &get_hostname_into_here);

char *get_hostname_into_here;
get_property(c, Hostname, &get_hostname_into_here);
printf("genericly retrieved hostname is %s (%p)\n", get_hostname_into_here, &get_hostname_into_here);
// Expect get_hostname_into_here not to be NULL, but it is.

полный исходный код, например (с выводом) .

Ответы [ 5 ]

1 голос
/ 04 сентября 2011

Там нет ответа на ваш вопрос, пока вы не предоставите более подробную информацию о функции get_property.Ясно, что параметр void *target используется для передачи внешнего «пробела», в который вы должны поместить результат - значение запрошенного свойства.

Какова природа этого пространства получателя?

В случае свойства int он довольно ясно формирует ваш код: указатель указывает на некоторый объект int, в который вы должны поместить значение свойства.Что вы делаете правильно.

А как же свойства строки?Здесь есть как минимум две возможности

1) Параметр void *target указывает на начало буфера char [], который предположительно достаточно велик для получения любого значения свойства.В этом случае ваш код должен выглядеть следующим образом

case Hostname:
  {
    char *hostname = target;
    strcpy(hostname, c->hostname);
  }
  break;

Функция в этом случае будет называться

char hostname_buffer[1024];
get_property(c, Hostname, hostname_buffer);

Это на самом деле «правильный» способ сделать это, за исключением того, чтовам нужно предпринять определенные шаги, чтобы убедиться, что вы не переполнили целевой буфер каким-либо длинным значением свойства.

2) Параметр void *target указывает на указатель типа char *, который долженполучить указатель hostname из свойства.(В этом случае target на самом деле содержит значение char **.) Код будет выглядеть как

case Hostname:
  {
    char **hostname = target;
    *target = c->hostname;
  }
  break;

Функция в этом случае будет называться

char *hostname;
get_property(c, Hostname, &hostname);

ЭтоВторой вариант мне не подходит, так как в этом случае вы по сути возвращаете указатель на внутренние данные структуры свойств.Не стоит предоставлять внешнему миру доступ к внутренним элементам [предположительно непрозрачной] структуры данных.

PS Как правило, нет необходимости явно приводить к и от void * указателей на языке C.

1 голос
/ 04 сентября 2011

Как я уже говорил в моих комментариях выше, невозможно дать точный ответ, потому что не ясно, какова цель здесь. Но я вижу две возможности:

1: target указывает на буфер char

Если это так, то может показаться, что вам нужно скопировать содержимое вашей строки в этот буфер. Это невозможно сделать безопасно, потому что вы не знаете, насколько велик приемный буфер. Но если вас это не волнует, вам нужно сделать что-то вроде:

strcpy((char *)target, c->hostname);

2: target указывает на char *

Если это так, то предполагается, что предполагается либо изменить этот char *, чтобы он указывал на существующую строку, либо динамически создать новый буфер, скопировать строку и затем изменить char *, чтобы он указывал это.

Так что либо:

char **p = (char **)target;
*p = c->hostname; 

или

char **p = (char **)target;
*p = malloc(strlen(c->hostname)+1);
strcpy(p, c->hostname);

Примечание

Вы получите предупреждение, потому что в этой строке:

*hostname = c->hostname;

*hostname относится к типу char, тогда как c->hostname относится к типу char *. Компилятор говорит вам, что это преобразование не имеет никакого смысла. На вашем месте я бы настроил ваш компилятор так, чтобы предупреждения воспринимались как ошибки (например, с флагом -Werror для GCC), потому что предупреждения всегда должны соблюдаться!

1 голос
/ 04 сентября 2011

Функция get_property должна быть изменена таким образом, чтобы target представлял собой двойной указатель на пустоту, то есть вы можете изменить сам указатель (не только память, к которой он относится):

void get_property (config *c, enum Property p, void **target) {
  switch (p) {
    case Port:
      *((int *) (*target)) = c->port;
      break;

    case Hostname:
      *target = c->config;
      break;
  }
}

А затем используйте такую ​​функцию:

int port;
int *pport = &port;
char *hostname;
get_propery(c, Port, &pport);
get_propery(c, Hostname, &hostname);
0 голосов
/ 04 сентября 2011

get_hostname_into_here определяется как:

char *get_hostname_into_here;

И вы передаете ссылку на него, а именно char**. В get_property вы преобразуете void* в char* вместо char**, а затем разыменовываете его перед назначением. Чтобы правильно получить строку, используйте:

case Hostname:
{
   char **hostname;
   hostname = (char **) target;
   *hostname = c->hostname;
}
break;
0 голосов
/ 04 сентября 2011

Давайте рассмотрим упрощенный пример вашей get_property функции, которая одинакова во всех важных аспектах:

void get_property_hostname(config* c, void* target) {
    char * hostname = (char *) target;
    *hostname = c->hostname;
}

В первой строке функции вы создаете указатель "char *"который указывает на то же место, что и «цель».Во второй строке функции, когда вы пишете *hostname = ..., вы записываете в char, на которое указывает имя хоста, поэтому вы записываете в первый байт памяти, на который указывает target.Это не то, что вы хотите;Вы предоставляете только один байт данных вызывающей функции.Кроме того, компилятор жалуется, потому что левая часть назначения имеет тип «char», а правая часть имеет тип «char *».

Существует как минимум три правильных способачтобы вернуть строку в C:

1) Вернуть указатель на исходную строку

Если вы сделаете это, у пользователя будет доступ к строкеи может изменить его, если захочет.Вы должны сказать ему не делать этого.Помещение в него константного квалификатора поможет достичь этого.

const char * get_property_hostname(config* c) {
    return c->hostname;
}

2) Дублируйте строку и верните указатель на дубликат

Если вы сделаете это,вызывающая функция должна передать дублирующую строку в free(), когда он закончит с ее использованием.См. документацию по strdup .

const char * get_property_hostname(config * c) {
    return strdup(c->hostname);
}

3) Записать строку в буфер, который выделил вызывающий

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

void get_property_hostname(config * c, char * buffer, int buffer_size)
{
    if (strlen(c->hostname)+1 > buffer_size)
    {
        // Avoid buffer overflows and return the empty string.
        buffer[0] = 0;
    }
    else
    {
        strcpy(buffer, c->hostname);
    }
}

Затем, чтобы использовать эту функцию, вы можете сделать что-то вроде:

void foo(){
    char buffer[512];
    get_property_hostname(c, buffer, sizeof(buffer));
    ...
    // buffer is on stack, so it gets freed automatically when foo returns
}

РЕДАКТИРОВАТЬ 1: Я оставлю это в качестве упражнения для вас, чтобы выяснить, как интегрировать идеи, представленные здесь, обратно в вашу общую функцию get_property.Если вы потратите время, чтобы понять, что здесь происходит, это не должно быть слишком сложно, но вам, возможно, придется добавить некоторые дополнительные параметры.

РЕДАКТИРОВАТЬ 2: Вот как вы могли быАдаптируйте метод 1, чтобы использовать пустой указатель, который указывает на символ * вместо использования возвращаемого значения:

void get_property_hostname(config* c, void * target) {
    *(char **)target = c->hostname;
}

Тогда вы бы назвали его так:

void foobar() {
    char * name;
    get_property_hostname(c, &name);
    ...
}
...