有没有办法将结构类型传递给C函数?

3

我有一些代码,其中多个函数非常相似,根据结构中一个字段的内容查找列表中的项目。这些函数之间唯一的区别是发生查找的结构类型。如果我可以传入类型,就可以消除所有代码重复。

我还注意到这些函数中也发生了一些互斥锁定,因此我认为最好不要动它们...

8个回答

7

如果您确保该字段在每个这样的结构中都放置在相同的位置,那么您可以简单地进行指针转换以访问该字段。这种技术在许多低级系统库(例如BSD套接字)中使用。

struct person {
  int index;
};

struct clown {
  int index;
  char *hat;
};

/* we're not going to define a firetruck here */
struct firetruck;


struct fireman {
  int index;
  struct firetruck *truck;
};

int getindexof(struct person *who)
{
  return who->index;
}

int main(int argc, char *argv[])
{
  struct fireman sam;
  /* somehow sam gets initialised */
  sam.index = 5;

  int index = getindexof((struct person *) &sam);
  printf("Sam's index is %d\n", index);

  return 0;
}

虽然这样做会失去类型安全,但这是一种有价值的技术。

[ 我现在已经测试了上面的代码并修复了各种小错误。当你有编译器时,这会更容易。 ]


需要注意的是,不要出现结构体打包的情况。在一个结构体中作为第一个字段的'char'类型,在另一个结构体中可能不会以相同的偏移量存储。 - itj
除非您明确要求,否则结构的布局是由平台 ABI 决定的。我猜默认情况下执行重新排序和压缩的此类 ABI 并不真正违法 (例如被 ISO C 标准禁止),但这样做相当疯狂。我认为很多现有软件在这样的平台上根本无法正常运行。 - tialaramex
1
@itj:对于结构体的第一个字段,不存在将char类型的项放置在偏移量0以外的风险。如果该字段出现在其他位置,则可能会出现问题。 - Jonathan Leffler

3
由于结构体只是预定义的内存块,您可以这样做。 您可以将void *传递给结构体,并将整数或其他内容传递给定义类型。
从那里开始,最安全的方法是在访问数据之前将void *重新转换为适当类型的指针。
需要非常小心,因为当您将其强制转换为void *时,会失去类型安全性,这样做可能会导致难以调试的运行时错误。

1
如果您测试的ID字段是所有结构体共享的公共初始序列的一部分,则使用联合可以保证访问将起作用。
#include <stdio.h>

typedef struct
{
    int id;
    int junk1;
} Foo;

typedef struct
{
    int id;
    long junk2;
} Bar;

typedef union
{
    struct
    {
        int id;
    } common;

    Foo foo;
    Bar bar;
} U;

int matches(const U *candidate, int wanted)
{
    return candidate->common.id == wanted;
}

int main(void)
{
    Foo f = { 23, 0 };
    Bar b = { 42, 0 };

    U fu;
    U bu;

    fu.foo = f;
    bu.bar = b;

    puts(matches(&fu, 23) ? "true" : "false");
    puts(matches(&bu, 42) ? "true" : "false");

    return 0;
}

如果你不幸的话,而且这个字段在不同的结构体中出现在不同的偏移量上,你可以在函数中添加一个偏移参数。然后,使用offsetof和包装宏模拟OP所要求的 - 在调用站点传递结构体类型:
#include <stddef.h>
#include <stdio.h>

typedef struct
{
    int id;
    int junk1;
} Foo;

typedef struct
{
    int junk2;
    int id;
} Bar;

int matches(const void* candidate, size_t idOffset, int wanted)
{
    return *(int*)((const unsigned char*)candidate + idOffset) == wanted;
}

#define MATCHES(type, candidate, wanted) matches(candidate, offsetof(type, id), wanted)

int main(void)
{
    Foo f = { 23, 0 };
    Bar b = { 0, 42 };
    puts(MATCHES(Foo, &f, 23) ? "true" : "false");
    puts(MATCHES(Bar, &b, 42) ? "true" : "false");

    return 0;
}

1

我认为你应该看一下C标准函数qsort()和bsearch(),以获取灵感。这些是通用代码,用于对数组进行排序并在预排序的数组中搜索数据。它们适用于任何类型的数据结构 - 但是您需要向它们传递一个指向执行比较的帮助函数的指针。帮助函数知道结构的细节,因此可以正确地进行比较。

实际上,由于您想要进行搜索,可能只需要bsearch(),但如果您正在动态构建数据结构,则可能决定需要与排序列表不同的结构。(您可以使用排序列表 - 它只会使事情变慢,与堆相比,例如。但是,您需要一个通用的heap_search()函数和一个heap_insert()函数才能正确完成工作,而这些函数在C中没有标准化。通过搜索网络,可以发现这样的函数存在 - 不是以那个名称命名的;只是不要尝试“c heap search”,因为假定您的意思是“cheap search”,您会得到大量垃圾!)


0

你可以用参数化宏来实现这个,但是大多数编程规范都会不赞同这种做法。


#include 
#define getfield(s, name) ((s).name)

typedef struct{
  int x;
}Bob;

typedef struct{
  int y;
}Fred;

int main(int argc, char**argv){
    Bob b;
    b.x=6;

    Fred f;
    f.y=7;

    printf("%d, %d\n", getfield(b, x), getfield(f, y));
}

0
一种方法是将类型字段作为结构的第一个字节。您的接收函数查看此字节,然后根据其发现的内容将指针转换为正确的类型。另一种方法是将类型信息作为单独的参数传递给每个需要它的函数。

0

简短回答:不行。但是,你可以自己创建一种方法来实现这样的结构体,即提供一个创建此类结构体的规范。然而,这通常是不必要的,也不值得花费精力;只需通过引用传递即可。(callFuncWithInputThenOutput(input, &struct.output);


-1

我对 C 有点生疏,但可以尝试在函数参数中使用 void* 指针作为变量类型。然后将结构体的地址传递给函数,并像平常一样使用它。

void foo(void* obj);

void main()
{
  struct bla obj;
  ...
  foo(&obj);
  ...
}

void foo(void* obj)
{
  printf(obj -> x, "%s")
}

这根本无法编译。你不能像那样解引用一个 void 指针。 - 1800 INFORMATION
嗯,公平地说,我确实说过我在C语言方面有点生疏。 ;) - Charles Graham

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接