【问题标题】:Deep Copying Structs深度复制结构
【发布时间】:2020-10-19 19:11:52
【问题描述】:

我已经查看了 Stack Overflow 上有关 Deep Copying 结构的示例,但这些示例不适用于我的情况。我试图模拟一个简单的缓存,用户可以更改每组的集数和行数。但是,在设置集合时,我意识到每条线都指向相同的东西,因此当您更改 Set 0 中的 Line 0 时,它也会更改 Set 1 中的 Line 0。有关如何使集合不具有相同行的任何建议或者更好的方法来设置缓存结构?

typedef struct Line {
    unsigned int valid;
    unsigned int tag;
    unsigned int lru;
}Line;

typedef struct Set {
    Line lines[0];
}Set;

Set *MakeSet(int n) {
    Set *s;
    s = malloc(sizeof s->lines *n);

    return s;
}

typedef struct Cahce {
    Set sets[0];
}Cache;

Cache *MakeCache(int n){
    Cache *c;
    c = malloc(sizeof c->sets *n);

    return c;
}

int main(void) {

int lines_per_set =1;
int num_sets = 2;

Set *s = MakeSet(lines_per_set);

    Line generic_line;
    generic_line.valid = 0;
    generic_line.tag =0;
    generic_line.lru =0;

    for(int i=0; i<lines_per_set; i++){
        s->lines[i] = generic_line;
    }

    Cache *c = MakeCache(num_sets);

    for(int j=0; j<num_sets; j++){
        c->sets[j] = *s;
    }

  c->sets[0].lines[0].valid =1;
  printf("%d", c->sets[1].lines[0].valid );

return 0;
}

【问题讨论】:

  • C 不支持维度为 0 的数组。如果您的编译器接受它作为扩展,那么它并不意味着您可能认为它的意思。
  • 特别是,sizeof s-&gt;linessizeof c-&gt;sets 并不像您认为的那样。此外,您将遇到struct Setstruct Cahce [原文如此] 不代表它们分别包含多少行和集合的问题。
  • 关于灵活数组成员,您需要了解的一点是它们的内容不会被赋值运算符复制。

标签: c caching struct deep-copy


【解决方案1】:

在大多数硬件中,每个缓存的集合数每个集合的行数是固定的。

但是,您的示例有点混淆了固定/变量。所以,[对我来说]你想要什么样的复杂程度并不清楚。

注意,在下面的示例中,没有一个重复的函数。只是进行初始创建。这是第一步。下面的代码应该会给你一些关于如何继续的想法。


这是一个所有参数都已修复的重构示例:

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

enum {
    LINES_PER_SET = 1,
    NUM_SETS = 2
};

typedef struct {
    unsigned int valid;
    unsigned int tag;
    unsigned int lru;
} Line;

typedef struct {
    int set_linecount;
    Line set_lines[LINES_PER_SET];
} Set;

typedef struct {
    int cache_setcount;
    Set cache_sets[NUM_SETS];
} Cache;

Cache *
MakeCache(void)
{
    Cache *c;

    c = calloc(1,sizeof(Cache));

    return c;
}

int
main(void)
{

    Cache *c = MakeCache();

    c->cache_sets[0].set_lines[0].valid = 1;
    printf("%d\n", c->cache_sets[0].set_lines[0].valid);

    return 0;
}

这是一个重构示例,它允许动态定义集合计数和行数:

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

typedef struct {
    unsigned int valid;
    unsigned int tag;
    unsigned int lru;
} Line;

typedef struct {
    int set_linecount;
    Line *set_lines;
} Set;

typedef struct {
    int cache_setcount;
    Set *cache_sets;
} Cache;

Line *
MakeLine(Line *line)
{

    if (line == NULL)
        line = malloc(sizeof(Line));

    line->valid = 0;
    line->tag = 0;
    line->lru = 0;

    return line;
}

Set *
MakeSet(Set *s,int nline)
{
    Line *line;

    if (s == NULL)
        s = malloc(sizeof(Set));

    s->set_linecount = nline;
    s->set_lines = malloc(sizeof(Line) * nline);

    for (int lineidx = 0;  lineidx < s->set_linecount;  ++lineidx) {
        line = &s->set_lines[lineidx];
        MakeLine(line);
    }

    return s;
}

Cache *
MakeCache(Cache *c,int nset,int nline)
{
    Set *s;

    if (c == NULL)
        c = malloc(sizeof(Cache));

    c->cache_setcount = nset;
    c->cache_sets = calloc(nset,sizeof(Set));

    for (int setidx = 0;  setidx < c->cache_setcount;  ++setidx) {
        s = &c->cache_sets[setidx];
        MakeSet(s,nline);
    }

    return c;
}

int
main(void)
{
    int lines_per_set = 1;
    int num_sets = 2;

    Cache *c = MakeCache(NULL,num_sets,lines_per_set);

    c->cache_sets[0].set_lines[0].valid = 1;
    printf("%d\n", c->cache_sets[0].set_lines[0].valid);

    return 0;
}

这是使用额外间接级别的第三个示例:

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

typedef struct {
    unsigned int valid;
    unsigned int tag;
    unsigned int lru;
} Line;

typedef struct {
    int set_linecount;
    Line **set_lines;
} Set;

typedef struct {
    int cache_setcount;
    Set **cache_sets;
} Cache;

Line *
MakeLine(void)
{
    Line *line;

    line = malloc(sizeof(Line));

    line->valid = 0;
    line->tag = 0;
    line->lru = 0;

    return line;
}

Set *
MakeSet(int nline)
{
    Line *line;
    Set *s;

    s = malloc(sizeof(Set));

    s->set_linecount = nline;
    s->set_lines = malloc(sizeof(Line *) * nline);

    for (int lineidx = 0;  lineidx < s->set_linecount;  ++lineidx)
        s->set_lines[lineidx] = MakeLine();

    return s;
}

Cache *
MakeCache(int nset,int nline)
{
    Cache *c;

    c = malloc(sizeof(Cache));

    c->cache_setcount = nset;
    c->cache_sets = calloc(nset,sizeof(Set *));

    for (int setidx = 0;  setidx < c->cache_setcount;  ++setidx)
        c->cache_sets[setidx] = MakeSet(nline);

    return c;
}

int
main(void)
{
    int lines_per_set = 1;
    int num_sets = 2;

    Cache *c = MakeCache(num_sets,lines_per_set);

    c->cache_sets[0]->set_lines[0]->valid = 1;
    printf("%d\n", c->cache_sets[0]->set_lines[0]->valid);

    return 0;
}

更新:

感谢您的解释。我最喜欢第二个。你将如何释放分配的内存?我试过了:

for (int i = 0; i < num_sets; i++) {
   for (int j = 0; j < lines_per_set; j++)
       free(c->sets[i].lines[j]);
}
free(c);

但是说行中的元素不是正确的类型。我以为它们是指针。

虽然可以一次释放所有东西,但最好使用一些子函数来完成这项工作。就像Make* 函数一样,我们可以创建Free* 函数。

一个好的格言可能是:如果一个函数[或语句]“看起来”过于复杂或容易出错,则可能需要将其拆分为更小的函数。

事实上,在下面的示例中,我将Make* 函数拆解为Free* 函数。

在最初的Make* 函数中,函数被传递了一个指向它正在创建/初始化的结构的指针。如果该指针是NULL,它正在执行malloc。因此,它“知道”它是否在执行malloc

这允许该函数使用malloc [as MakeCache did] 创建一个“独立”结构,或者让该结构成为另一个结构的从属(例如,Set 从属于Cache 和@ 987654337@ 隶属于Set)。这可能不是最好的方法[单独的“alloc”和“init”函数可能更简洁]。

然而,对于Free* 函数,调用者 必须“告诉”函数是否执行结构指针的free。这会很混乱,因为调用者必须跟踪 [此类] 事情。通常,跟踪这些是各个职能部门的工作。

因此,我为每个结构添加了一个额外的成员,以“记住”结构是否已分配。然后,Free* 函数可以查看它以确定它是否可以释放其指针。这是我在生产代码中经常使用的个人“交易技巧”。该字段可能只是一个布尔值int(如cache_alloced)。但是,我选择使用带有各种选项位的位掩码(例如OPT_ALLOC)。这是为了在未来需要时进行扩展。

我创建了一些辅助 CPP 宏来帮助完成此任务 [小心你想要的,你可能真的得到它 :-)]。

您可能会注意到各种FREEME* 宏首先检查“即将被释放”的指针是否为NULL,如果是则禁止调用free。这并不是真正必要的,因为 允许使用空指针调用 free [即它是无害的]。

他们做的另一件事是,在free 之后,他们将指针设置为NULL。这是一个“安全”功能,用于捕获“已释放”指针的误用/重用。它还可以防止来自free 的“双重释放”中止(例如free(ptr); free(ptr); 会导致这种情况)。

如果指针为空,则取消引用可能会成功,但结果未定义。那是 UB [未定义的行为],但可能很难检测到。通过将指针清零,指针的任何后续取消引用都会立即触发SIGSEGV [segfault]。

旁注:我遵循了你命名函数的约定。而且,虽然我在这里没有这样做,但我发现颠倒“宾语”和“动词”很有用。也就是说,而不是(例如)MakeCacheFreeCache,我会这样做:CacheMakeCacheFree。这样,如果有更多函数在给定的结构指针上运行,如果我们生成 所有 个函数的排序列表,它们就会“排队”。

无论如何,这就是我将如何释放结构:

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

typedef unsigned int u32;

#define OPT_ALLOC       (1u << 0)           // 1=struct has been malloc'ed

#define ALLOCME(_ptr) \
    _ptr = malloc(sizeof(*_ptr))

#define ALLOCMEIF(_ptr,_opt) \
    do { \
        if (_ptr != NULL) { \
            _ptr->_opt = 0; \
            break; \
        } \
        ALLOCME(_ptr); \
        _ptr->_opt = OPT_ALLOC; \
    } while (0)

#define FREEME(_ptr) \
    do { \
        if (_ptr == NULL) \
            break; \
        free(_ptr); \
        _ptr = NULL; \
    } while (0)

#define FREEMEIF(_ptr,_opt) \
    do { \
        if (_ptr == NULL) \
            break; \
        if (_ptr->_opt & OPT_ALLOC) { \
            free(_ptr); \
            _ptr = NULL; \
        } \
    } while (0)

typedef struct {
    u32 line_opt;
    u32 valid;
    u32 tag;
    u32 lru;
} Line;

typedef struct {
    u32 set_opt;
    int set_linecount;
    Line *set_lines;
} Set;

typedef struct {
    u32 cache_opt;
    int cache_setcount;
    Set *cache_sets;
} Cache;

Line *
MakeLine(Line *line)
{

    ALLOCMEIF(line,line_opt);

    line->valid = 0;
    line->tag = 0;
    line->lru = 0;

    return line;
}

Set *
MakeSet(Set *s,int nline)
{
    Line *line;

    ALLOCMEIF(s,set_opt);

    s->set_linecount = nline;
    s->set_lines = malloc(sizeof(Line) * nline);

    for (int lineidx = 0;  lineidx < s->set_linecount;  ++lineidx) {
        line = &s->set_lines[lineidx];
        MakeLine(line);
    }

    return s;
}

Cache *
MakeCache(Cache *c,int nset,int nline)
{
    Set *s;

    ALLOCMEIF(c,cache_opt);

    c->cache_setcount = nset;
    c->cache_sets = calloc(nset,sizeof(Set));

    for (int setidx = 0;  setidx < c->cache_setcount;  ++setidx) {
        s = &c->cache_sets[setidx];
        MakeSet(s,nline);
    }

    return c;
}

Line *
FreeLine(Line *line)
{

    FREEMEIF(line,line_opt);

    return line;
}

Set *
FreeSet(Set *s)
{
    Line *line;

    for (int lineidx = 0;  lineidx < s->set_linecount;  ++lineidx) {
        line = &s->set_lines[lineidx];
        FreeLine(line);
    }

    FREEME(s->set_lines);
    FREEMEIF(s,set_opt);

    return s;
}

Cache *
FreeCache(Cache *c)
{
    Set *s;

    for (int setidx = 0;  setidx < c->cache_setcount;  ++setidx) {
        s = &c->cache_sets[setidx];
        FreeSet(s);
    }

    FREEME(c->cache_sets);
    FREEMEIF(c,cache_opt);

    return c;
}

int
main(void)
{
    int lines_per_set = 1;
    int num_sets = 2;

    Cache *c = MakeCache(NULL,num_sets,lines_per_set);

    c->cache_sets[0].set_lines[0].valid = 1;
    printf("%d\n", c->cache_sets[0].set_lines[0].valid);

    c = FreeCache(c);

    return 0;
}

【讨论】:

  • 谢谢。这很有帮助。
猜你喜欢
  • 2012-10-04
  • 1970-01-01
  • 2019-10-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多