如何隐藏C中的某些struct字段?

问题描述 投票:12回答:4

我正在尝试实现一个struct人,我需要隐藏一些字段或使它们保持不变。创建私有字段的技巧。

标题:

#pragma once

#define NAME_MAX_LEN 20

typedef struct _person {
    float wage;
    int groupid;
} Person;

const char const *getName (Person *p);
int getId (Person *p);

/// OTHER FUNCTIONS

资源

#include "person.h"


struct _person
{
    int id;

    float wage;
    int groupid;

    char name[NAME_MAX_LEN];
};

/// FUNCTIONS

海湾合作委员会说person.c:7:8: error: redefinition a 'struct _person' struct _person

我可以在标题中写这个,但在它之后,我不能使用结构的字段。

typedef struct _person Person;
c typedef
4个回答
16
投票

C没有隐藏结构类型的单个成员的机制。但是,通过仅根据指向此类型的指针进行操作,而不提供定义,可以使整个类型不透明。然后,用户必须使用您提供的功能以任何方式操作实例。这是有时完成的事情。

在某种程度上,您可能能够实现类似于您使用隐藏上下文描述的内容。例如,考虑一下:

header.h

typedef struct _person {
    float wage;
    int groupid;
} Person;

implementation.c

struct _person_real {
    Person person;  // must be first, and is a structure, not a pointer.
    int id;
    char name[NAME_MAX_LEN];
};

现在你可以这样做:

Person *create_person(char name[]) {
    struct _person_real *pr = malloc(sizeof(*pr));

    if (pr) {
        pr->person.wage = DEFAULT_WAGE;
        pr->person.groupid = DEFAULT_GROUPID;
        pr->id = generate_id();
        strncpy(pr->name, name, sizeof(pr->name));
        pr->name[sizeof(pr->name) - 1] = '\0';

        return &pr->person;  // <-- NOTE WELL
    } else {
        return NULL;
    }
}

指向结构的第一个成员的指针也始终指向整个结构,因此如果客户端将从该函数获得的指针传递给您,则可以

struct _person_real *pr = (struct _person_real *) Person_pointer;

并从更大的背景下研究成员。

但是,请注意,这样的方案存在风险。没有什么可以阻止用户在没有更大的上下文的情况下创建Person,并将指针传递给期望上下文对象存在的函数。还有其他问题。

总的来说,C API通常采用不透明结构方法,或者只是仔细记录允许客户端访问他们有权访问的数据,甚至只记录一切工作方式,以便用户可以自己做出选择。这些,尤其是后者,与整体C方法和习语完全一致 - C不会握住您的手,或保护您免受伤害。它相信你知道你在做什么,只做你想做的事。


24
投票

结构不能有多个冲突的定义。因此,您无法创建隐藏某些字段的结构。

您可以做什么,但它声明结构存在于标头中而不定义它。然后调用者被限制为仅使用指向结构的指针并使用实现中的函数来修改它。

例如,您可以按如下方式定义标题:

typedef struct _person Person;

Person *init(const char *name, int id, float wage, int groupid);

const char *getName (const Person *p);
int getId (const Person *p);
float getWage (const Person *p);
int getGroupid (const Person *p);

您的实施将包含:

#include "person.h"

struct _person
{
    int id;

    float wage;
    int groupid;

    char name[NAME_MAX_LEN];
};

Person *init(const char *name, int id, float wage, int groupid)
{
    Person *p = malloc(sizeof *p);
    strcpy(p->name, name);
    p->id = id;
    p->wage= wage;
    p->groupid= groupid;
    return p;
}

...

3
投票

你可以使用mixin风格;例如写在标题中:

struct person {
    float wage;
    int groupid;
};

struct person *person_new(void);
char const *getName (struct person const *p);
int getId (struct person const *p);

在源头

struct person_impl {
    struct person   p;
    char            name[NAME_MAX_LEN];
    int             id;
}

struct person *person_new(void)
{
    struct person_impl *p;

    p = malloc(sizeof *p);
    ...
    return &p->p;
}

chra const *getName(struct person const *p_)
{
    struct person_impl *p =
           container_of(p_, struct person_impl, p);

    return p->name;
}

参见例如https://en.wikipedia.org/wiki/Offsetof有关container_of()的详细信息。


2
投票

John Bollinger的回答补遗:

虽然,恕我直言,具有访问器函数(init / get / set / destroy)的不透明指针类型是最安全的方法,但是还有另一个选项允许用户在堆栈上放置对象。

可以将单个“无类型”内存块作为struct的一部分进行分配,并明确地使用该内存(逐位/逐字节)而不是使用其他类型。

即:

// public
typedef struct {
    float wage;
    int groupid;
    /* explanation: 1 for ID and NAME_MAX_LEN + 1 bytes for name... */
    unsigned long private__[1 + ((NAME_MAX_LEN + 1 + (sizeof(long) - 1)) / sizeof(long))];
} person_s;

// in .c file (private)
#define PERSON_ID(p) ((p)->private__[0])
#define PERSON_NAME(p) ((char*)((p)->private__ + 1))

这是一个非常强大的指标,应该避免访问private__成员中的数据。无法访问实现文件的开发人员甚至不知道其中的内容。

话虽如此,最好的方法是opaque类型,正如您在使用pthread_t API(POSIX)时可能遇到的那样。

typedef struct person_s person_s;
person_s * person_new(const char * name, size_t len);
const char * person_name(const person_s * person);
float person_wage_get(const person_s * person);
void person_wage_set(person_s * person, float wage);
// ...
void person_free(person_s * person);

笔记:

  1. 用指针避免typedef。它只会让开发人员感到困惑 保持指针显式更好,因此所有开发人员都可以知道他们使用的类型是动态分配的。 编辑:此外,通过避免“typedefing”指针类型,API承诺未来/替代实现也将在其API中使用指针,允许开发人员信任并依赖于此行为(请参阅注释)。
  2. 当使用opaque类型时,可以避免使用NAME_MAX_LEN,允许任意长度的名称(假设重命名需要一个新对象)。这是优选不透明指针方法的额外动机。
  3. 尽可能避免将_放在标识符的开头(即_name)。以_开头的名称被认为具有特殊含义,有些是保留的。对于以_t结尾的类型(由POSIX保留)也是如此。 请注意我如何使用_s将类型标记为结构,我不使用_t(保留)。
  4. C更常见于snake_case(至少在历史上)。最着名的API和大多数C标准都是snake_case(除了从C ++导入的东西)。 此外,保持一致更好。在某些情况下使用CamelCase(或smallCamelCase)而将snake_case用于其他事情可能会让开发人员试图记住您的API时感到困惑。

1
投票

John Bollinger写的是一种利用结构和内存如何工作的简洁方法,但它也是一种简单的方法来获得一个段错误(想象一下分配一个Person数组,然后将最后一个元素传递给一个访问id或's'的'方法'名称),或破坏你的数据(在Person的数组中,下一个Person正在覆盖之前的Person的'私人'变量)。你必须记住你必须创建一个指向Person而不是Person数组的指针数组(听起来非常明显,直到你决定优化某些东西并认为你可以比初始化函数更有效地分配和初始化结构)。

不要误会我的意思,这是解决问题的好方法,但是在使用它时你必须要小心。我建议(虽然每个Person使用4/8字节更多内存)是创建一个struct Person,它有一个指向另一个结构的指针,该结构只在.c文件中定义并保存私有数据。这样在某个地方犯一个错误就更难了(如果它是一个更大的项目,那么相信我 - 你迟早会这样做)。

.h文件:

#pragma once

#define NAME_MAX_LEN 20

typedef struct _person {
    float wage;
    int groupid;

    _personPriv *const priv;
} Person;

void personInit(Person *p, const char *name);
Person* personNew(const char *name);

const char const *getName (Person *p);
int getId (Person *p);

.c文件:

typedef struct {
    int id;
    char name[NAME_MAX_LEN];
} _personPriv;

const char const *getName (Person *p) {
    return p->priv->name;
}

int getId (Person *p) {
    return p->priv->id;
}

_personPriv* _personPrivNew(const char *name) {
    _personPriv *ret = memcpy(
        malloc(sizeof(*ret->priv)),
        &(_personPriv) {
            .id = generateId();
        },
        sizeof(*ret->priv)
    );

    // if(strlen(name) >= NAME_MAX_LEN) {
    //     raise an error or something?
    //     return NULL;
    // }

    strncpy(ret->name, name, strlen(name));

    return ret;
}

void personInit(Person *p, const char *name) {
    if(p == NULL)
        return;

    p->priv = memcpy(
        malloc(sizeof(*p->priv)),
        &(_personPriv) {
            .id = generateId();
        },
        sizeof(*p->priv)
    );

    ret->priv = _personPrivNew(name);
    if(ret->priv == NULL) {
        // raise an error or something
    }
}

Person* personNew(const char *name) {
    Person *ret = malloc(sizeof(*ret));

    ret->priv = _personPrivNew(name);
    if(ret->priv == NULL) {
        free(ret);
        return NULL;
    }
    return ret;
}

附注:可以实现此版本,以便在结构的“公共”部分之后/之前分配私有块以改善位置。只需分配sizeof(Person) + sizeof(_personPriv)并将一部分初始化为Person,将第二部分初始化为_personPriv

© www.soinside.com 2019 - 2024. All rights reserved.