如何在C中隐藏某些结构字段?

时间:2019-03-22 20:41:17

标签: c typedef

我正在尝试实现一个struct person,我需要隐藏一些字段或使其恒定。 创建私有字段的技巧。

标题:

samplerCube

来源

#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

5 个答案:

答案 0 :(得分:20)

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

可以执行的操作会声明该结构存在于标头中,而没有对其进行定义。然后,调用者将被限制为仅使用指向该结构的指针,并在实现中使用函数对其进行修改。

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

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

...

答案 1 :(得分:10)

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

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

header.h

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

实施。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不能握住您的手,或保护您免受伤害。它让您知道自己在做什么,并且只做自己打算做的事。

答案 2 :(得分:2)

您可以使用混合样式;例如在标题中写:

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()的详细信息。

答案 3 :(得分:2)

约翰·波林格答案的附录:

尽管恕我直言,带有访问器功能(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__成员中的数据。无法访问实现文件的开发人员甚至都不知道其中包含什么。

话虽如此,最好的方法是使用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。它只会使开发人员感到困惑。

    最好将指针保持明确,这样所有开发人员都可以知道他们使用的类型是动态分配的。

    编辑:此外,通过避免“类型定义”指针类型,API承诺将来的/替代实现也将在其API中使用指针,从而使开发人员可以信任并依赖此行为(请参见注释)。

  2. 使用不透明类型时,可以避免使用NAME_MAX_LEN,允许使用任意长度的名称(假设重命名需要一个新对象)。这是偏爱不透明指针方法的一个额外诱因。

  3. 尽可能避免将_放在标识符的开头(即_name)。以_开头的名称被认为具有特殊含义,并且其中一些保留。对于以_t结尾的类型(POSIX保留)也是如此。

    请注意,我如何使用_s将类型标记为结构,而不使用_t(保留)。

  4. C更经常是snake_case(至少从历史上来说)。最为人所知的API和大多数C标准是snake_case(从C ++导入的东西除外)。

    此外,保持一致更好。当开发人员尝试记住您的API时,在某些情况下使用CamelCase(或smallCamelCase)在将snake_case用于其他事情时会造成混淆。

答案 4 :(得分:0)

John Bollinger写的是一种利用结构和内存工作方式的巧妙方法,但是它也是获取段错误的一种简便方法(想象一下分配Person的数组,然后再传递最后一个元素更改为访问ID或名称的“方法”,或破坏您的数据(在Person数组中,下一个Person将覆盖前一个Person的“私有”变量) 。您必须记住,必须创建指向Person的指针数组,而不是Person的数组(听起来很明显,直到您决定优化某些内容并认为可以分配和初始化结构更多为止)比初始化函数更有效)。

不要误会我的意思,这是解决问题的好方法,但是在使用时必须小心。 我建议(尽管每个Person使用4/8字节以上的内存)是创建一个结构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