【问题标题】:Passing Void type parameter in C在 C 中传递 Void 类型参数
【发布时间】:2010-12-19 01:38:47
【问题描述】:

您好,我正在使用 C 语言进行赋值,我需要将未知类型的参数传递给函数。

例如,假设我有以下内容:

int changeCount(void* element)
{
    element.Count = element.Count++;

    return 1;

}

变量元素之所以为void,是因为有3种可能性。然而,所有 3 个都有一个名为“Count”的成员变量。

当我尝试编译我在 Eclipese 中编写的实际代码时,我收到以下错误:

错误:对成员“计数”的请求 不是结构或联合的东西

我猜这是因为编译器事先不知道“元素”的类型。但是我不明白为什么这不起作用。

感谢您的帮助!

【问题讨论】:

  • 为什么不直接写element.Count++ 而不是调用函数呢?
  • 与问题没有直接关系,但element.Count = element.Count++; 的行为未定义。为了简单起见,我将这样陈述规则:您不能在同一个表达式中多次修改一个值。有关完整的详细信息,请参阅 C 标准中的“6.5 表达式”。

标签: c void-pointers


【解决方案1】:

您需要将指针转换为这 3 种类型之一,然后使用它。比如:

MyType* p = (MyType*)element;
p->count++;

但是,您需要确定要转换的对象的类型,因为将对象转换为错误的类型可能很危险。

【讨论】:

  • 或者赋值给适当类型的局部变量:例如struct foo * f = element; (无需演员)
  • 感谢您的回复。但是,如果我事先不知道 MyType 是什么,这将不起作用吗?
  • 你要知道...没有出路
  • 只有一种方法:知道 Count 的偏移量。如果offset为0,可以*((int *) element)++;等等。
【解决方案2】:

首先,您传入一个指向 void 的指针,这对于未知类型是一种有效的方法,但您需要传入指向不同对象类型的指针。

C 不是动态语言,因此符号类型信息在运行前会大量删除,因此当您说您的三种类型都有成员 Count 时,这对您的函数设计没有帮助。

访问Count 的唯一方法是在使用->(*element).Count 解除引用之前将void* 参数转换为正确的指针类型(即不仅仅是.)。

除非您依赖于具有兼容布局的类型(这可能取决于实现),否则您还需要传递一些东西来帮助您的函数确定要执行的正确转换。在这一点上,使用三个独立的函数和更好的类型安全性可能会更好。

【讨论】:

  • 你的解释澄清了很多事情。谢谢!!
【解决方案3】:

您必须将其转换为实际类型,但如果结构体在同一位置没有 count 属性,您可能会得到意想不到的结果。

typedef struct _A 
{
    int count;
    int x;
}A;
int changeCount(void* element)
{
    ((A*)element)->Count++;

    return 1;

}

还请记住,如果您将指针传递给如下所示的结构,您将增加 x 字段而不是计数。

typedef struct _B 
{
    int x;      
    int count;

}B;

    B st;
    B* pst = &st;
    changeCount(pst);

【讨论】:

  • 如果您要对函数内部的已知结构进行类型转换,那么 void* 的目的是什么?不能直接放A*元素吗?我仍然不明白 void* 是如何用作参数的:/
  • 如果您想使用一个函数来操作许多不同类型的结构。它是一种多态性。
  • 但是在你上面的例子中你是类型转换为 A*,在那种情况下为什么要使用 void* ?
【解决方案4】:

使用演员表。

ClassName(element).count++

还有你的 element.Count = element.Count++;行是多余的,你只需要做 element.count++(将值加一)

【讨论】:

  • C++ 的函数样式转换不是有效的 C 语言,一般来说,将指针类型 (void*) 转换为对象不太可能工作。
【解决方案5】:

这正是问题所在:

我猜这正在发生 因为编译器不知道 事先输入“元素”的类型。然而 我不明白为什么这不起作用。

按名称调用方法或成员数据通常不是静态类型语言的特性。

即使 void * 也只能可靠地转换为 T *,其中 T 是一种类型,而指针实际上是指向该类型的指针。

在 C++ 中,一种可能性是让所有三种类型都继承自同一个虚拟低音类 X,该类 X 具有方法 Count(即接口 ICountable)。然后投射到X * 并使用p->Count。这将是惯用的 C++ - 所有类都实现相同的接口,因此它们都支持这种方法。这将是一种语言支持的方法,类似于依赖于 Tommy McGuire 的回答中显示的相同结构偏移技巧,这使得所有结构都相似按照惯例。如果您要更改结构,或者编译器要偏离布局结构的标准,那么您将陷入困境。

我不禁认为这是一个玩具问题,因为该方法非常简单,通常不会将其包装在函数中 - 只需将其称为内联:T t; t.Count++;

【讨论】:

  • 我猜这行不通:“从同一个虚拟低音类 X 继承”——问题是关于 C,而不是 C++。
  • 除非标签具有误导性,否则这是 C,而不是 C++。
  • 我将进行编辑以明确最后一句是 C++ 设计成语。
【解决方案6】:

如果.Count 元素对于这三种类型中的每一种都属于同一类型,那么您也可以使用宏。假设它是 int,你可以这样做:

#define changeCount(a) _changeCount((a), &(a)->Count)

int _changeCount(void* element, int *count)
{
  (*count)++;
  return 1;
}

这会起作用,因为a.Count 地址将在您调用函数时被解析,而不是在之后(当您不再知道类型时)。我假设您在调用该函数时具有正确的类型。所以Sometype x; changeCount(x); 会起作用,但是传递已经是(void*) 的东西不会。

你原来的表达element.Count = element.Count++;也很奇怪。如果要增加,请使用element.Count++element.Count = element.Count + 1

【讨论】:

  • +1: &(a)->Count 应该是 &(a->Count) - 还有,你为什么还要把元素放在 _changeCount 中
  • @Cade - 我将元素留在那里,以防 OP 想在该功能中做更多事情,但给了我们一个简化版本。另外——我认为&(a)->Count 是正确的——-> 的优先级高于&,所以地址解析没问题,但是你的版本对于changeCount(some_ptr+4) 不能正常工作
【解决方案7】:

编译时,C 会丢弃[1] 大部分类型信息,只留下偏移量。所以,你的函数会编译成类似这样的伪代码:


changeCount:
  assign *(*(stack_ptr + offset_element) + offset_Count) + 1
    to *(stack_ptr + offset_element) + offset_Count;
  assign 1 to return_value;
  pop

stack_ptr是调用changeCount时创建的栈帧的位置,offset_element是元素参数的位置,相对于stack_ptr,但是offset_Count是什么?请记住,编译器对您的代码的所有了解就是您在示例中显示的内容; element 是一个通用指针,实际上并不是指向任何东西的指针。您必须通过强制转换或将其分配给变量[2]来告诉编译器指向的元素:


typedef struct { int Count; } counted_t;
int changeCount(void* element)
{
    counted_t* counted = element;
    counted.Count++;
    return 1;
}

此函数将生成与上述基本相同的(伪)代码,但编译器现在知道 Count 的偏移量应该是多少。

您提到元素指向的类型有三种可能性。有几种处理方法:一个显着的联合或一个“继承”的结构。对于一个有区别的联合使用,比如说,一个结构,一个元素是一个枚举,标识三个可能性中的哪一个,另一个元素是三个可能结构的联合;这大致就是 ML 语言(OCaml、Haskell 等)所称的代数数据类型或 Pascal 中的联合。对于“继承”,您可以使用类型定义:


typedef struct { counted_t counted; int i; } counted_int_t;
typedef struct { counted_t counted; double d; } counted_double_t;
typedef struct { counted_t counted; char* s; } counted_charptr_t;

在这种情况下,您可以使用上面的 changeCount 函数并传入指向 counted_int_t、counted_double_t 或 counted_charptr_t 的指针。

发生的情况是,只要 counted_t 元素在前,编译器就会将三个结构与“后代”结构中的 Count 元素布置在同一位置。 (至少,在我用过的每一个编译器和我见过的每一段代码中。我认为这在某些时候使它成为了 C 标准,但这是一个非常正常的习语。)

[1] 调试信息除外,如果您已告诉编译器发出它。但是,您的程序将无权访问该信息,因此在这种情况下无济于事。

[2] x++ (postincrement) 操作递增它所应用的变量(嗯,左值);原代码中的赋值是不必要的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-11-12
    • 1970-01-01
    • 2014-06-29
    • 2021-07-03
    • 2020-09-13
    相关资源
    最近更新 更多