Использование функции, возвращающей указатель как lvalue

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

Вот пример кода:

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

void printIntArray(void** array, size_t length) {
  printf("Array at %p\n", array);
  while (length--) {
    printf("  [%zu] at %p -> %p", length, array + length, *(array + length));
    if (*(array + length)) {
      printf(" -> %d", *(int*)*(array + length));
    }
    printf("\n");
  }
}

void* getElement(void** array, size_t index) {
  return *(array + index);
}

int main(int argc, char** argv) {
  const size_t n = 5;
  size_t i;

  /* n element array */
  void** test = malloc(sizeof(void*) * n);
  i = n;
  while (i--) {
    *(test + i) = NULL;
  }

  /* Set element [1] */
  int testData = 123;
  printf("testData at %p -> %d\n", &testData, testData);
  *(test + 1) = (void*)&testData;

  printIntArray(test, n);

  /* Prints 123, as expected */
  printf("Array[1] = %d\n", *(int*)getElement(test, 1));

  /* Something new in [2] */
  /* FIXME  lvalue required as left operand of assignment */
  int testThing = 456;
  getElement(test, 2) = (void*)&testThing;
  printIntArray(test, n);

  return 0;
}

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


person Xophmeister    schedule 26.06.2014    source источник
comment
зачем тебе это?   -  person nightshade    schedule 26.06.2014
comment
@nightshade Простой пример того, почему вы можете это сделать, упоминается в одном из ответов на этот stackoverflow Is errno thread-safe, который имеет реализацию errno, которая является потокобезопасной из-за использования errno, являющегося макросом, который использует функцию, которая возвращает указатель int на память errno конкретного потока.   -  person Richard Chambers    schedule 26.06.2014
comment
@nightshade В качестве упражнения я писал библиотеку динамических массивов с проверкой границ. Приведенный выше код примера был упрощен только для того, чтобы проиллюстрировать проблему.   -  person Xophmeister    schedule 27.06.2014


Ответы (4)


Реализуйте getElement() следующим образом:

void ** getElement(void ** ppv, size_t index) {
  return ppv + index;
}

И используйте это так:

 *getElement(test, 2) = &testThing;

Если бы вы использовали макрос, вы могли бы даже пойти так, как задумали:

#define GET_ELEMENT(ppv, index) \
  (*(ppv + index))

Используйте это так:

GET_ELEMENT(test, 2) = &testThing;
person alk    schedule 26.06.2014

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

Другими словами,

void *vPtr = malloc(35);

*((int *)vPtr) = 5;   // legal since cast void * to int *
 *vPtr = 5;           // illegal since size of the memory location pointed to is unknown.

Тот же принцип применим к функции, которая возвращает пустой указатель.

Вам просто нужно привести указатель, возвращаемый функцией, к типу, допускающему присваивание.

Простой пример, который компилируется в Visual Studio 2005:

void *xPtr (int *pp)
{
    return pp;
}

void jj ()
{
    int jjj;
    *( (int *)xPtr(&jjj)) = 6;
}

Где, как если бы вы изменили функцию jj() на

void jj ()
{
    int jjj;
    *(xPtr(&jjj)) = 6;
}

вы увидите ошибки компиляции, такие как

1>c:\fileobject.c(191): error C2100: illegal indirection
1>c:\fileobject.c(191): warning C4047: '=' : 'void *' differs in levels of indirection from 'int'
1>c:\fileobject.c(191): error C2106: '=' : left operand must be l-value
person Richard Chambers    schedule 26.06.2014
comment
ОП имеет в виду void **, а не void *. Можно очень хорошо разыменовать имя, а затем что-то присвоить, а именно другое void*. - person alk; 26.06.2014

getElement(test, 2) = (void*)&testThing;

Это никогда не сработает. Вы не можете присвоить значение функции. Использовать это:

test[2] = (void*)&testThing;

Обратите внимание, что getElement(test, 2) возвращает NULL, это не указатель на индекс элемента 2 вашего массива test, это его значение (тоже указатель, но не тот, который вам нужен). Что бы вы ни делали с информацией, которую вам предоставил getElement(test, 2), вы никогда не сможете использовать ее для изменения test[2].

Скажем, в ситуации, когда у вызывающего абонента нет доступа к test, вам придется написать функцию setElement().

void setElement(void** array, size_t index, void* value) {
  array[index] = value;
}
...
setElement(test, 2, (void*)&testThing);
person Havenard    schedule 26.06.2014

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

typedef struct 
{
   int x;
} st;

st f()
{
   st p;
   p.x = 20;
   return p;
}

int main()
{
    f().x = 9;
}

Вы возвращаете значение из функции... вам нужно присвоить это значение чему-то, прежде чем использовать его. Да, указатель следует тому же правилу, разница в том, что его значение является адресом чего-то... поэтому вам нужно будет назначить возврат вашей функции переменной void * и тому, что вы хотите сделать с этой переменной.

В моем примере, чтобы заставить его работать, вам нужно что-то вроде:

int main()
{
     st v = f();
     v.x = 9;
}

Та же логика подходит для вашего указателя

person nightshade    schedule 26.06.2014