在大多数硬件中,每个缓存的集合数和每个集合的行数是固定的。
但是,您的示例有点混淆了固定/变量。所以,[对我来说]你想要什么样的复杂程度并不清楚。
注意,在下面的示例中,没有一个重复的函数。只是进行初始创建。这是第一步。下面的代码应该会给你一些关于如何继续的想法。
这是一个所有参数都已修复的重构示例:
#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]。
旁注:我遵循了你命名函数的约定。而且,虽然我在这里没有这样做,但我发现颠倒“宾语”和“动词”很有用。也就是说,而不是(例如)MakeCache 和 FreeCache,我会这样做:CacheMake 和 CacheFree。这样,如果有更多函数在给定的结构指针上运行,如果我们生成 所有 个函数的排序列表,它们就会“排队”。
无论如何,这就是我将如何释放结构:
#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;
}