C 使用函数返回指针作为左值

C 使用函数返回指针作为左值,c,pointers,void-pointers,lvalue,C,Pointers,Void Pointers,Lvalue,假设我想动态分配一个可以保存任何数据的数组,即使用C语言中的void**作为我的类型。然后,我想要一个返回元素地址的简单函数,而不是每次重新写入指针算术逻辑。为什么我不能在赋值中使用此函数(即,这样我可以设置并获取元素的值) 下面是一些示例代码: #include <stdio.h> #include <stdlib.h> void printIntArray(void** array, size_t length) { printf("Array at %p\n"

假设我想动态分配一个可以保存任何数据的数组,即使用C语言中的
void**
作为我的类型。然后,我想要一个返回元素地址的简单函数,而不是每次重新写入指针算术逻辑。为什么我不能在赋值中使用此函数(即,这样我可以设置并获取元素的值)

下面是一些示例代码:

#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;
}
#包括
#包括
无效打印阵列(无效**阵列,大小\u t长度){
printf(“位于%p\n的数组”,数组);
while(长度--){
printf(“[%zu]位于%p->%p”,长度,数组+长度,*(数组+长度));
如果(*(数组+长度)){
printf(“->%d”,*(int*)*(数组+长度));
}
printf(“\n”);
}
}
void*getElement(void**数组,大小\u t索引){
返回*(数组+索引);
}
int main(int argc,字符**argv){
常数大小n=5;
尺寸i;
/*n元阵列*/
void**test=malloc(sizeof(void*)*n);
i=n;
而(我--){
*(测试+i)=空;
}
/*集合元素[1]*/
int testData=123;
printf(“位于%p->%d\n的testData,&testData,testData);
*(测试+1)=(无效*)和测试数据;
打印阵列(测试,n);
/*按预期打印123*/
printf(“数组[1]=%d\n”,*(int*)getElement(test,1));
/*[2]中的新事物*/
/*作为赋值的左操作数所需的FIXME左值*/
int testThing=456;
getElement(test,2)=(void*)&testThing;
打印阵列(测试,n);
返回0;
}

这不是第一次问这个问题,但答案通常是“您试图给函数赋值,而不是函数的返回值,这是不允许的”。这很公平,但我们能绕开它吗?我有点困惑

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;
}

同样的逻辑也适用于指针

C使用指向伪
的指针,其他语言称之为引用。。。以下是您正在尝试做的一个示例:

typedef struct 
{
   int x;
} st;

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

int main()
{
    f().x = 9;
}
getElement(test, 2) = (void*)&testThing;
您正在从函数返回一个值。。。在使用之前,您需要将该值分配给某个对象。是的,指针遵循相同的规则,区别在于它的值是某个对象的地址。。。因此,您必须将函数的返回赋值给void*变量,以及对该变量执行的操作

在我的示例中,要使其工作,您需要:

int main()
{
     st v = f();
     v.x = 9;
}
同样的逻辑也适用于指针

getElement(test, 2) = (void*)&testThing;
这是行不通的。不能为函数赋值。使用以下命令:

test[2] = (void*)&testThing;
请注意,
getElement(test,2)
返回
NULL
,它不是指向数组
test
的元素索引
2
,而是它的值(这也是一个指针,但不是您需要的指针)。无论您如何处理
getElement(test,2)
提供的信息,都不能使用它来更改
test[2]

假设调用方无权访问
test
,则必须编写
setElement()
函数

void setElement(void** array, size_t index, void* value) {
  array[index] = value;
}
...
setElement(test, 2, (void*)&testThing);
这是行不通的。不能为函数赋值。使用以下命令:

test[2] = (void*)&testThing;
请注意,
getElement(test,2)
返回
NULL
,它不是指向数组
test
的元素索引
2
,而是它的值(这也是一个指针,但不是您需要的指针)。无论您如何处理
getElement(test,2)
提供的信息,都不能使用它来更改
test[2]

假设调用方无权访问
test
,则必须编写
setElement()
函数

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

不允许向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.
同样的原理也适用于返回空指针的函数

您只需将函数返回的指针强制转换为允许赋值的类型

在VisualStudio2005中编译的一个简单示例是

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

不允许向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.
同样的原理也适用于返回空指针的函数

您只需将函数返回的指针强制转换为允许赋值的类型

在VisualStudio2005中编译的一个简单示例是

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
实现
getElement()
如下:

void ** getElement(void ** ppv, size_t index) {
  return ppv + index;
}
 *getElement(test, 2) = &testThing;
GET_ELEMENT(test, 2) = &testThing;
然后像这样使用它:

void ** getElement(void ** ppv, size_t index) {
  return ppv + index;
}
 *getElement(test, 2) = &testThing;
GET_ELEMENT(test, 2) = &testThing;

如果使用宏,您甚至可以按照预期的方式:

#define GET_ELEMENT(ppv, index) \
  (*(ppv + index))
像这样使用它:

void ** getElement(void ** ppv, size_t index) {
  return ppv + index;
}
 *getElement(test, 2) = &testThing;
GET_ELEMENT(test, 2) = &testThing;
实现
getElement()
如下:

void ** getElement(void ** ppv, size_t index) {
  return ppv + index;
}
 *getElement(test, 2) = &testThing;
GET_ELEMENT(test, 2) = &testThing;
然后像这样使用它:

void ** getElement(void ** ppv, size_t index) {
  return ppv + index;
}
 *getElement(test, 2) = &testThing;
GET_ELEMENT(test, 2) = &testThing;

如果使用宏,您甚至可以按照预期的方式:

#define GET_ELEMENT(ppv, index) \
  (*(ppv + index))
像这样使用它:

void ** getElement(void ** ppv, size_t index) {
  return ppv + index;
}
 *getElement(test, 2) = &testThing;
GET_ELEMENT(test, 2) = &testThing;

为什么要这样做?@nightshade在这个stackoverflow的一个答案中提到了一个简单的例子,它有一个
errno
实现,是线程安全的,因为
errno
是一个宏,它使用一个函数返回一个指向特定线程的int指针
errno
memory.@nightshade作为练习,我正在编写一个带有边界检查的动态数组库。T