使用函数参数作为变量

问题描述 投票:0回答:4

在函数内部重新分配函数参数是不好还是好的做法还是未定义的行为?

让我解释一下我正在尝试用一个例子,这里的函数:

void
gkUpdateTransforms(GkNode *node /* other params */) {
  GkNode *nodei;

  if (!(nodei = node->chld))
    return;

  do {
    /* do job */
    nodei = nodei->next;
  } while (nodei);
}

替代方案:

void
gkUpdateTransforms2(GkNode *node /* other params */) {

  /* node parameter is only used here to get chld, not anywhere else */
  if (!(node = node->chld))
    return;

  do {
    /* do job */
    node = node->next;
  } while (node);
}

我检查了程序集输出,看起来一样,我们不需要在第二个声明变量。您可能会问,如果参数类型已更改,但第一个条件的条件相同,因为它也需要更新。

编辑:参数是按值传递,我的意图不是编辑指针本身

EDIT2:递归函数怎么样?如果gkUpdateTransforms2是递归的,会发生什么?我很困惑,因为函数会调用自己,但我认为在每个调用中,参数将是不同的堆栈

c function c99 c89 function-parameter
4个回答
4
投票

我不知道为什么你认为这将是未定义的行为 - 事实并非如此。主要是编码风格,没有明显的对错。

通常,将参数视为immutable objects是一种好习惯。保留函数输入的未触摸副本很有用。因此,使用仅仅是参数副本的局部变量可能是个好主意。正如您所看到的,这不会影响性能 - 编译器将优化代码。

但是,如果您写入参数也不是什么大问题。这也是常见的做法。称这样做不好的做法会非常迂腐。

一些迂腐的编码风格使得所有函数参数const如果它们不应该被修改,但我个人认为这只是混淆,这使代码更难阅读。在你的情况下,这样的迂腐风格将是void gkUpdateTransforms(GkNode*const node)。不要与const正确性混淆,这是一个普遍好的东西而不仅仅是一种风格问题。


但是,您的代码中有一些东西肯定被认为是不好的做法,那就是内部条件的赋值。尽可能避免这种情况,这很危险并且使代码更难阅读。大多数情况下没有任何好处。

在C的历史早期就注意到混合===的危险。为了解决这个问题,在20世纪80年代,人们想出了像"yoda conditions"这样的脑损伤的东西。然后在1989年左右出现了Borland Turbo C,其中有一个花哨的警告功能“可能是错误的分配”。这就是Yoda条件的死亡,自那时起编制者就已经警告不要在条件下进行任务。

确保您当前的编译器为此发出警告。也就是说,确保从1989年开始使用比Borland Turbo更糟糕的编译器。是的,市场上的编译器更糟糕。

(gcc给出“警告:建议用作真值的赋值括号”)


我会把代码编写为

void gkUpdateTransforms(GkNode* node /* other params */) 
{
  if(node == NULL)
  {
    return ;
  }

  for(GkNode* i=node->chld; i!=NULL; i=i->next;)
  {
    /* do job */
  }
}

这主要是风格上的变化,使代码更具可读性。它不会提高性能。


2
投票

恕我直言,这并不是一个“坏”的做法,但如果没有更好的方法,那就值得质疑自己。关于你对汇编程序输出的分析:它可以作为一个有趣和教育背景幕后但你不建议使用它作为优化的理由或更糟糕的源代码中的懒惰。下一个编译器或下一个架构可能只会使你的思考完全失效 - 我的建议是留在Knuth这里:"Instead of imagining that our main task is to instruct a computer what to do, let us concentrate rather on explaining to human beings what we want a computer to do."

在你的代码中我认为决定是50:50,没有明确的赢家。我认为node-iterator是一个自己的概念,证明了一个单独的编程结构(在我们的例子中只是一个变量)但是这个函数再次非常简单,以至于我们在下一个清晰度方面没有取得多少成功。程序员看着你的代码,所以我们可以很好地使用第二个版本。如果你的函数开始变异并且随着时间的推移而增长,那么这个前提可能会变得无效,而我们的第一个版本就更好了。也就是说,我会像这样编写第一个版本:

void
gkUpdateTransforms(GkNode *node /* other params */) {    
  for (GkNode *nodei = node->chld; nodei != NULL; nodei = nodei->next) {
      /* do job */
  }
}

1
投票

这是明确定义的,是实现此行为的完美方法。

您可能将其视为问题的原因是执行以下操作时常见的错误:

int func(object a) {
    modify a // only modifying copy, but user expects a to be modified

但在你的情况下,你希望复制指针。


1
投票

只要它按值传递,就可以安全地将其视为任何其他局部变量。在这种情况下并非坏习惯,也不是未定义的行为。

© www.soinside.com 2019 - 2024. All rights reserved.