【问题标题】:Typedef abstract type pointer to concrete type pointer to avoid casting in c programmingTypedef 抽象类型指针指向具体类型指针,以避免在 c 编程中进行强制转换
【发布时间】:2019-10-17 18:59:09
【问题描述】:

我有一个头文件,其中包含对抽象数据类型的指针进行操作以隐藏头文件中的依赖关系的函数。为了适应多个平台,在头文件中声明的函数在源代码树的不同文件中有多个实现/平台,每个都使用自己的具体底层类型来实现接口。当然,最终程序中只有一个实现链接,具体取决于构建中选择的平台。

foo.h

#pragma once
typedef struct foo Foo; // abstract: never defined in the program and used as pointer
Foo* foo_create(void);
void foo_transmogrify(Foo* f);

fooImplBar.c

#include "foo.h"
#include "bar.h" // from a library that defines Bar 

Foo* foo_create(void)
{
    Bar* bar = bar_create();
    return (Foo*) bar; // the cast is needed here to avoid the error
}

void foo_transmogrify(Foo* f)
{
     bar_transmogrify((Bar*) f); // another cast here
}

fooImplBaz.c

#include "foo.h"
#include "baz.h" // from a library that defines Baz

Foo* foo_create(void)
{
    Baz* baz = baz_create();
    return (Foo*) baz; // the cast is needed here to avoid the error
}

void foo_transmogrify(Foo* f)
{
     baz_transmogrify((Baz*) f); // another cast here
}

有没有办法避免这些强制转换?

换句话说,有没有办法在各自的文件夹中将Bar*Baz* 键入Foo*?我无法在实现文件夹中使用Foo 定义更改BarBaz 定义,因为它们(Bar 和Baz)来自库。因此,此处在标头中前向声明并在 .c 文件中定义的模式不适用。 避免强制转换的一种方法是使用void* 而不是完全使用Foo*,代价是丢失类型信息,我发现这比强制转换更糟糕。

【问题讨论】:

  • 如果类型真的都是foo 只是针对不同的板,为什么不让你的foo_create 函数根据识别板的预处理器条件将正确的结构与return 相关联?
  • Per C 2018 6.2.5 28,“所有指向结构类型的指针都应具有彼此相同的表示和对齐要求。”这与其他规则一起,意味着如果您创建一个Bar 对象,将指向它的指针转换为Foo *,让其他一些代码拥有Foo * 一段时间,然后返回Foo * 并将其转换为 Bar *,然后您就有了指向原始 Bar 对象的指针,并且没有违反任何 C 规则或由于这些操作而与未定义的行为发生冲突。

标签: c interface casting typedef


【解决方案1】:

变体 1

您可以尝试使用联合。这将使接口“foo”依赖于 all 实现(这里:“bar”和“baz”)。优点是无需更改实现。

foo.h

#include "bar.h"
#include "baz.h"

typedef union {
    Bar *bar;
    Baz *baz;
} Foo;

Foo foo_create(void);
void foo_transmogrify(Foo f);

fooImplBar.c

#include "foo.h"

Foo foo_create(void)
{
    Foo foo;
    foo.bar = bar_create();
    return foo;
}

void foo_transmogrify(Foo f)
{
    bar_transmogrify(f.bar);
}

fooImplBaz.c

#include "foo.h"

Foo foo_create(void)
{
    Foo foo;
    foo.baz = baz_create();
    return foo;
}

void foo_transmogrify(Foo f)
{
    baz_transmogrify(f.baz);
}

您保留所有类型信息。

我在一个简单的项目中尝试过,但 BarBaz 在各自的标题中没有完全定义。它也应该适用于完整类型。

bar.h

typedef struct bar Bar;
Bar* bar_create(void);
void bar_transmogrify(Bar* f);

bar.c

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

#include "bar.h"

typedef struct bar {
    int i;
} Bar;

Bar* bar_create(void)
{
    Bar* bar = malloc(sizeof (Bar));
    bar->i = 23;
    return bar;
}

void bar_transmogrify(Bar* f)
{
    printf("Bar: %d\n", f->i);
}

baz.h

typedef struct baz Baz;
Baz* baz_create(void);
void baz_transmogrify(Baz* f);

baz.c

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

#include "baz.h"

typedef struct baz {
    float f;
} Baz;

Baz* baz_create(void)
{
    Baz* baz = malloc(sizeof (Baz));
    baz->f = 3.14159;
    return baz;
}

void baz_transmogrify(Baz* f)
{
    printf("Baz: %f\n", f->f);
}

main.c

#include "foo.h"

int main(void)
{
    Foo f;
    f = foo_create();
    foo_transmogrify(f);
    return 0;
}

是的,我知道,如果在某些严重的情况下使用此代码,则会出现内存泄漏。需要一些ba[rz]_destroy()。但这只是一个很容易解决的小问题。

编译过程如下:

gcc -Wall -Wextra -pedantic -c fooImplBar.c -o fooImplBar.o
gcc -Wall -Wextra -pedantic -c fooImplBaz.c -o fooImplBaz.o
gcc -Wall -Wextra -pedantic -c bar.c -o bar.o
gcc -Wall -Wextra -pedantic -c baz.c -o baz.o
gcc -Wall -Wextra -pedantic -c main.c -o main.o
gcc -Wall -Wextra -pedantic main.o fooImplBar.o bar.o -o mainImplBar
gcc -Wall -Wextra -pedantic main.o fooImplBaz.o baz.o -o mainImplBaz

变体 2:

每个实现都完成了未完全定义的结构。您可能需要调整您的实现,因为它需要使用相同的类型名称。

“foo.h”和你的一样。

fooImplBar.c

#include "foo.h"
#include "bar.h"

Foo* foo_create(void)
{
    Foo* bar = bar_create();
    return bar;
}

void foo_transmogrify(Foo* f)
{
    bar_transmogrify(f);
}

fooImplBaz.c

#include "foo.h"
#include "baz.h"

Foo* foo_create(void)
{
    Foo* baz = baz_create();
    return baz;
}

void foo_transmogrify(Foo* f)
{
     baz_transmogrify(f);
}

您保留类型信息,因为它是相同的类型。但是,每个实现都会以自己的方式定义类型。

bar.h

typedef struct foo {
    int i;
} Foo;

Foo* bar_create(void);
void bar_transmogrify(Foo* f);

bar.c

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

#include "bar.h"

Foo* bar_create(void)
{
    Foo* bar = malloc(sizeof (Foo));
    bar->i = 23;
    return bar;
}

void bar_transmogrify(Foo* f)
{
    printf("Bar: %d\n", f->i);
}

baz.h

typedef struct foo {
    float f;
} Foo;

Foo* baz_create(void);
void baz_transmogrify(Foo* f);

baz.c

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

#include "baz.h"

Foo* baz_create(void)
{
    Foo* baz = malloc(sizeof (Foo));
    baz->f = 3.14159;
    return baz;
}

void baz_transmogrify(Foo* f)
{
    printf("Baz: %f\n", f->f);
}

main.c

#include "foo.h"

int main(void)
{
    Foo* f;
    f = foo_create();
    foo_transmogrify(f);
    return 0;
}

编译就像变体 1 中一样。


如果我正确理解您的介绍,则不同的实现可能位于不同的文件夹中。然后,您可以将文件、类型和函数都命名为相同的名称,因为您不会在一个应用程序中使用多个。

这有点简单,但可能与您的设置不兼容。

这就是示例的外观。 “foo.h”和“main.c”是相同的。

bar/foo.c

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

#include "../foo.h"

typedef struct foo {
    int i;
} Foo;

Foo* foo_create(void)
{
    Foo* foo = malloc(sizeof (Foo));
    foo->i = 23;
    return foo;
}

void foo_transmogrify(Foo* f)
{
    printf("Bar: %d\n", f->i);
}

baz/foo.c

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

#include "../foo.h"

typedef struct foo {
    float f;
} Foo;

Foo* foo_create(void)
{
    Foo* foo = malloc(sizeof (Foo));
    foo->f = 3.14159;
    return foo;
}

void foo_transmogrify(Foo* f)
{
    printf("Baz: %f\n", f->f);
}

您不再需要包装器“fooImplBar”和“fooImplBaz”。

编译:

gcc -Wall -Wextra -pedantic -c bar/foo.c -o bar/foo.o
gcc -Wall -Wextra -pedantic -c baz/foo.c -o baz/foo.o
gcc -Wall -Wextra -pedantic -c main.c -o main.o
gcc -Wall -Wextra -pedantic main.o bar/foo.o -o mainBar
gcc -Wall -Wextra -pedantic main.o baz/foo.o -o mainBaz

【讨论】:

  • 您的解决方案中的问题是,接口(foo.h)取决于具体的实现,而应该是应该依赖于接口的实现,如我的第一句所述问题。
  • 另外,可以说没有内存泄漏,因为指针总是在作用域内并且在 main() 退出时被释放。
  • 好吧,您认为您无法更改实现:“我无法更改 Bar 和 Baz 定义 [...]” 如果可以,请参阅变体 2。关于内存泄漏,它取决于您的实际应用。对于这个例子,你是对的,但它不是很好的风格。
猜你喜欢
  • 1970-01-01
  • 2021-09-20
  • 2011-12-29
  • 2015-12-28
  • 1970-01-01
  • 1970-01-01
  • 2020-06-15
  • 1970-01-01
相关资源
最近更新 更多