问题描述
我已经查看了有关深度复制结构的堆栈溢出示例,但这些示例不适用于我的情况。我试图模拟一个简单的缓存,用户可以更改设置数量和每组行数。但是,在设置集合时,我意识到每一行都指向同一事物,因此,当您更改集合0中的行0时,它也会更改集合1中的行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;
}
解决方法
在大多数硬件中,每个缓存的个集和每个集合的行的数量是固定的。
但是,您的示例将固定/变量混为一谈。因此,[对我来说]尚不清楚您想要什么水平的成熟度。
请注意,在下面的示例中,不是一个重复的函数。只是进行初始创建。那是第一步。下面的代码应为您提供一些有关如何进行的想法。
这是一个重构示例,其中所有参数均已固定:
#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
创建“独立式”结构(就像MakeCache
一样),也可以使该结构成为另一个结构的从属(例如Set
是从属的) Cache
和Line
的下属Set
)。这可能不是执行此操作的最佳方法(单独的“ alloc”和“ init”函数可能更简洁)。
但是,对于Free*
函数,调用方将必须“告诉”该函数是否执行结构指针的free
。这很麻烦,因为调用者必须跟踪[此类]事情。通常,保持跟踪是各个职能部门的职责范围。
因此,我向每个结构添加了一个额外的成员,以“记住”该结构是否已分配。然后,Free*
函数可以查看它以确定是否可以释放其指针。这是我个人在生产代码中经常使用的“交易技巧”。该字段只能是布尔值int
(如cache_alloced
中一样)。但是,我选择使用带有各种选项位的位掩码(例如OPT_ALLOC
)。这是为了在将来需要时进行扩展。
我已经创建了一些辅助CPP宏来协助完成此操作[请小心,您可能会得到它:-)]。
您可能会注意到,各种FREEME*
宏首先检查“即将释放”指针是否为NULL
,如果是,则禁止调用free
。这实际上不是必需的,因为允许 使用空指针调用free
[即它是无害的]。
他们要做的另一件事是,在free
之后,他们将指针设置为NULL
。这是一种“安全”功能,可以捕获对“释放”指针的滥用/重复使用。它还可以防止free
的“双重释放”中止(例如free(ptr); free(ptr);
会导致这种情况)。
如果指针 not 为空,则取消引用可能成功,但结果不确定。那是UB [未定义的行为],但可能很难检测到。通过将指针归零,指针的任何后续取消引用都将立即触发SIGSEGV
[段错误]。
旁注:我遵循了命名函数的约定。而且,尽管我在这里没有做,但是我发现反转“宾语”和“动词”很有用。也就是说,我将执行MakeCache
和FreeCache
,而不是CacheMake
和CacheFree
。这样,如果有更多函数在给定的struct指针上操作,则如果我们生成 all 函数的排序列表,它们将“排队”。
无论如何,这是释放结构的方法:
#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 nline)
{
Set *s;
ALLOCMEIF(c,cache_opt);
c->cache_setcount = nset;
c->cache_sets = calloc(nset,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,c->cache_sets[0].set_lines[0].valid);
c = FreeCache(c);
return 0;
}