Обмен объектов с помощью указателей - PullRequest
8 голосов
/ 10 февраля 2010

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

void swap(void *a, void *b, size_t size);

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

void *temp;
temp = a;
a = b;
b = temp;

только изменяет то, на что указывают указатели. Это верно? Если это правильно, почему замена указателей фактически не меняет содержимое между * a и * b. Потому что, если ваш указатель указывает на что-то другое, вы не можете разыменовать его, и объекты теперь будут другими?

Точно так же, просто переключая значения как:

void *temp;
*temp = *a;
*a = *b;
*b = *temp;

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

Означает ли подкачка объектов полный обмен памяти и значение, на которое указывает указатель?

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

void *temp = malloc(sizeof(pa));
// check for null pointer
temp = a;
// do something I'm not sure of since I don't quite get how allocating space is any 
// different than the two above methods???

Спасибо!

Ответы [ 10 ]

19 голосов
/ 10 февраля 2010

Замена указателей не приводит к изменению указанных значений. Если бы это было так, это было бы похоже на смену адресных этикеток на конвертах, которые перенесли бы меня в ваш дом, а вы - в мой.

Вы были почти там:

void swap(void *a, void *b, size_t size) {
  char temp[size]; // C99, use malloc otherwise
  // char serves as the type for "generic" byte arrays

  memcpy(temp, b,    size);
  memcpy(b,    a,    size);
  memcpy(a,    temp, size);
}

Функция memcpy копирует память, которая является определением объектов в C. (Вызывается POD или обычные данные в C ++, для сравнения.) Таким образом, memcpy - это то, как вы делаете назначение без заботясь о типе объекта, и вы могли бы вместо этого написать другие назначения как memcpy:

int a = 42, b = 3, temp;

temp = b;
b    = a;
a    = temp;
// same as:
memcpy(&temp, &b,    sizeof a);
memcpy(&b,    &a,    sizeof a);
memcpy(&a,    &temp, sizeof a);

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


В качестве любопытства, еще одна версия, которая избегает malloc в обычных случаях и не использует VLA C99:

void swap(void *a, void *b, size_t size) {
  enum { threshold = 100 };
  if (size <= threshold) {
    char temp[threshold];

    memcpy(temp, b,    size);
    memcpy(b,    a,    size);
    memcpy(a,    temp, size);
  }
  else {
    void* temp = malloc(size);
    assert(temp); // better error checking desired in non-example code

    memcpy(temp, b,    size);
    memcpy(b,    a,    size);
    memcpy(a,    temp, size);

    free(temp);
  }
}
3 голосов
/ 10 февраля 2010

Чтобы ответить на ваш первый вопрос, давайте заполним некоторые значения, чтобы увидеть, что происходит:

void* a = 0x00001000; // some memory address
void* b = 0x00002000; // another memory address
/* Now we'll put in your code */
void* temp; // temp is garbage
temp = a; // temp is now 0x00001000
a = b; // a is now 0x00002000
b = temp; // b is now 0x00001000

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

Чтобы ответить на ваш второй вопрос, вы не можете разыменовать void*. Причина этого в том, что void не имеет размера, поэтому пытаться разыменовать или присваивать что-то без размера не имеет смысла. Таким образом, void* - это способ гарантировать, что вы можете указать на что-то , но вы никогда не узнаете, что это за что-то , без дополнительной информации (отсюда параметр size для вашей процедуры ).

Оттуда, зная указатель и размер данных, на которые указывает указатель, вы можете использовать процедуру типа memcpy для перемещения данных, на которые указывает один указатель, в местоположение, на которое указывает другой.

2 голосов
/ 02 августа 2012

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

    typedef unsigned char * ucp;

    void swap(void *a, void *b, int size){
      ucp c=(ucp)a;
      ucp d=(ucp)b;
      for(int i=0; i<size; i++){
        int temp=(int)c[i];
    c[i]=(int)d[i];
    d[i]=temp;
      }

    }

В основном это приводит к приведению обоих указателей к типу указателя без знака. Затем вы увеличиваете указатель, который в случае неподписанного символа увеличивает один байт за раз. Тогда вы в основном копируете содержимое каждого байта за раз в памяти. Если кто-то захочет исправить или уточнить это, я тоже буду признателен.

2 голосов
/ 10 февраля 2010

Параметры похожи на локальные переменные со значениями, скопированными в них до начала выполнения функции. Этот прототип:

void swap(void *a, void *b, size_t size);

Означает, что два адреса скопированы в новые переменные с именами a и b. Поэтому, если вы измените то, что хранится в a и b, ничто из того, что вы делаете, не окажет никакого эффекта после возврата swap.

1 голос
/ 10 февраля 2010

Ты рядом.

Проблема в том, что вы «меняете» только указатели a и b , которые являются локальными переменными в функции.

Я предполагаю, что вне функции у вас есть переменные, давайте их назовем:

void *x = ...;
void *y = ...;

При звонке:

swap(x, y, some_size);

a и b указывают на те же объекты, что и x и y соответственно. Теперь, когда вы поменяете местами a и b точек, x и y все еще указывают на то, на что они указывали раньше.

Чтобы изменить количество точек памяти x и y , вам нужно передать указатель на переменную x , поэтому указатель на указатель :)

Поскольку вы не можете изменить объявление функции, вы можете поменять местами только содержимое памяти, где x a ) и y b ) указывает на. Некоторые решения есть в других ответах :) Как правило, memcpy это то, что вы хотите.

1 голос
/ 10 февраля 2010

Если бы вы писали функцию для замены двух целых чисел с учетом указателей на них, ваше решение об обмене указанными значениями сработало бы. Однако рассмотрим ситуацию с

struct {
  int a;
  int b;
} a, b;

swap(&a, &b, sizeof(a));

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

1 голос
/ 10 февраля 2010

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

Самый простой способ сделать это с помощью memcpy - выделить буфер в стеке, memcpy соответствующий размер от a в него, memcpy от b до a и один последний memcpy от температуры до b.

0 голосов
/ 20 февраля 2016

Нам не нужно использовать memcpy для замены двух указателей, следующий код работает хорошо (проверено на обмен строк int * и char *):

void swap(void **p, void **q)
{
    void *t = *p;
    *p = *q;
    *q = t;
}
0 голосов
/ 10 февраля 2010

Чтобы получить какой-либо реальный эффект, вам нужно сделать эквивалент второго упомянутого вами блока:

void *temp;
*temp = *a;
*a = *b;
*b = *temp;

Проблема здесь в том, что void не имеет размера, поэтому вы не можете назначить void в том виде, в каком они есть. Вам нужно выделить место для temp, чтобы указать, а затем скопировать значения, используя что-то вроде memcpy().

0 голосов
/ 10 февраля 2010

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

Если ваша функция должна быть такой:

void swap(void *a, void *b, size_t size);

Полагаю, вам нужно реализовать что-то вроде:

void * temp;
temp = malloc(size);
memcpy(temp,a,size);
memcpy(a,b,size);
memcpy(b,temp,size);
free(temp);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...