gpt4 book ai didi

c - 良好做法:非变异函数何时应要求使用指针而不是副本?

转载 作者:行者123 更新时间:2023-11-30 14:39:57 24 4
gpt4 key购买 nike

考虑一个非常轻便的数据类型,以及一个将这种类型作为参数但不对其进行突变的函数-我试图了解何时将指针传递给它而不是简单地复制它。就一般的C良好做法而言。

在我看来,复制这样的轻量值对性能而言并不重要,并且指针可能比普通值引起更多的混乱。

例如,请考虑以下代码(摘自Bob Nystrom的“ Crafting Interpreters”一书):

typedef struct {
TokenType type;
const char* start;
int length;
int line;
} Token;


在以下代码中, identifiersEqual采用类型为 Token*的参数,而不是纯 Token。这可能很有意义-我们不必复制 Token

另一方面, addLocal采用普通的 Token

根据一般的C良好做法-我试图了解 identifiersEqual是否使用指针,而 addLocal使用副本是否有特定原因。这两个函数都不会改变值,并且再次- Token没有太大的作用。

这是我所缺少的模式,还是偶然?我应该在哪种情况下以这种方式决定?

static bool identifiersEqual(Token* a, Token* b) {
if (a->length != b->length) return false;
return memcmp(a->start, b->start, a->length) == 0;
}

static void addLocal(Token name) {
if (current->localCount == UINT8_COUNT) {
error("Too many local variables in function.");
return;
}

Local* local = &current->locals[current->localCount++];
local->name = name;
local->depth = -1;
}

static void declareVariable() {
if (current->scopeDepth == 0) return;

Token* name = &parser.previous;

for (int i = current->localCount - 1; i >= 0; i--) {
Local* local = &current->locals[i];
if (local->depth != -1 && local->depth < current->scopeDepth) break;
if (identifiersEqual(name, &local->name)) {
error("Variable with this name already declared in this scope.");
}
}

addLocal(*name);
}

最佳答案

这个问题正在征求意见,所以我应该避免说我可能不会在同一程序中使用这两个接口,因为我试图避免API有时通过值传递数据类型,而有时通过参考。但这仅是我,因此我将其余答案限制为您可能选择使用按值传递中等重对象的接口的原因。如果您想听听原始编码员选择此样式的原因,则应直接询问他。

首先要指出的是,大多数现代编译器如果可以访问被调用函数的主体,并且被调用函数本身足够轻巧,可以内联,则可以避免复制。这些条件似乎适用于所引用代码中的函数,因此使用按值调用可能没有成本。 [注1]因此,如果API样式为代码阅读器提供了有用的信息,则可以认为它是有用的。

现在考虑参数原型X const *X。在这两种情况下,我们都知道传递的参数不会被修改,因此我们当然不需要复制它。

但是,争论的持续时间仍然值得关注。如果被调用函数使用一个指针并将该指针保存到一个对象中,该对象将超出调用的寿命,那么我们将需要担心所传递对象的所有权。实际上,我们需要将对象的所有权传递给被调用的函数,并且还需要确保对象没有自动生存期。特别是,我们不能使用临时调用该函数,并且对于使用具有静态生存期(不能为free d的对象)调用该函数,我们可能会感到有些怀疑。

另一方面,按值进行呼叫显然不会对呼叫者施加任何要求。如果被调用的函数想要保存传递​​的对象,则它负责制作副本并在不再有用时处置副本。我们可以向它传递任何我们喜欢的对象:一个临时的,静态的或本地的对象,将在以后的调用中重复使用。

碰巧的是,令牌对象通常是在解析循环中重用的本地对象,而不是动态分配的对象,后者会带来更复杂的内存管理机制。在大多数情况下,仅会查询传递给函数的令牌对象,但有时确实需要保存它们。函数名称addLocal强烈建议此函数将保留传递的对象。

在这种特殊情况下,addLocal实际上确实保存了传递的对象,但保存了副本。它不能这样做,因为它传递了一个副本,并且该副本不会超过调用的寿命。幸运的是,优化器几乎可以肯定地内联addLocal,从而避免了不必要的中间副本。因此,这里的按值调用已准确地传达给了代码读取器,因此完全不必担心传递给addLocal的对象的生命周期。

identifiersEqual的情况下,似乎不太可能被调用的函数需要保留两个传递的对象中的任何一个,因此保证可能不太重要。但是,如上所述,出于一致性考虑,我可能也将identifiersEqual编写为按值调用,希望编译器能够完全避免复制。 (这是信仰的飞跃,我对一致性的追求可能是一种抽动。)



笔记


对于非常轻量级的对象和某些编译器,按值调用可能会产生更好的编译代码。例如,标准的64位ABI允许将适合八个字节的结构传递到寄存器中,如果仅出于将对象传递给函数的目的而构造对象,则这特别方便。我记得写OS X GUI API的那一天,小型几何对象总是按值传递小型几何对象,并且在编程指南中有一条注释说明这样做是为了提高效率。我不知道这是否仍然适用,但是我也不认为这种特殊的结构足够轻便。尽管可能不常见,但在其他上下文中,某些编译器明确表示从未使用对象的地址确实会使编译器生成更好的代码。

在此问题的上下文中,足以观察到的是,按值调用生成的代码(可能)不比按引用调用差。如果对您确实很重要,则必须检查您关心的编译器生成的代码。我还没做

关于c - 良好做法:非变异函数何时应要求使用指针而不是副本?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55819785/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com