C 使用函数参数作为变量

C 使用函数参数作为变量,c,function,c99,c89,function-parameter,C,Function,C99,C89,Function Parameter,在函数内部重新分配函数参数是好的还是坏的做法,或者可能是未定义的行为 让我用一个例子来解释我试图做什么,这里是函数: void gkUpdateTransforms(GkNode *node /* other params */) { GkNode *nodei; if (!(nodei = node->chld)) return; do { /* do job */ nodei = nodei->next; } while (nodei)

在函数内部重新分配函数参数是好的还是坏的做法,或者可能是未定义的行为

让我用一个例子来解释我试图做什么,这里是函数:

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是递归的,会发生什么?我很困惑,因为函数会自己调用,但我认为在每次调用中,参数都会是不同的堆栈

这是定义良好的,也是实现此行为的一个非常好的方法

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

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

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

只要按值传递,就可以安全地将其视为任何其他局部变量。在这种情况下,这是一种不错的做法,也不是未定义的行为。

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

通常情况下,将参数视为最佳实践。保留函数输入的未触及副本非常有用。因此,最好使用局部变量,它只是参数的一个副本。如您所见,这丝毫不会影响性能-编译器将优化代码

但是,如果您写入参数也没什么大不了的。这也是常见的做法。如果说这样做是不好的做法,那就太迂腐了

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


然而,在您的代码中有一些东西肯定被认为是不好的做法,那就是内部条件赋值。尽可能避免这种情况,因为这样做很危险,而且会使代码更难阅读。通常没有好处

早在C语言的历史上就注意到了把
=
=
混在一起的危险。为了解决这个问题,在20世纪80年代,人们发明了大脑受损的东西,比如。然后在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 */
  }
}
这主要是为了使代码更具可读性而进行的风格更改。这并不能大大提高性能。

我认为这并不完全是一种“糟糕”的做法,但如果没有更好的方法,就值得扪心自问。关于您对汇编程序输出的分析:这可能是一个有趣和有教育意义的幕后观察,但您不建议将其作为优化或更糟的是源代码懒惰的理由。下一个编译器或下一个体系结构可能会使您的思考完全无效-我的建议是在此处使用Knuth:

在你的代码中,我认为决定是50:50,没有明确的赢家。我认为节点迭代器是它自己的一个概念,它证明了一个单独的编程构造(在我们的例子中,它只是一个变量)的合理性,但是函数非常简单,我们无法为下一个查看代码的程序员赢得太多的清晰性,因此我们可以很好地使用第二个版本。如果你的函数随着时间的推移开始变异和增长,这个前提可能会变得无效,我们最好使用第一个版本。 也就是说,我会这样编写第一个版本:

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

该参数是函数的局部参数。修改其值与修改任何局部变量一样好或坏。只有当你以某种方式使它取一个不确定的值时,它才是UB。在函数中修改函数的参数是非常好的。这一点都不坏,做起来很好。当然不是未定义的行为。“这是唯一的UB,如果你以某种方式让它取一个不确定的值”我不知道怎么做?但根据评论,我似乎处于安全区域,在一个结构良好的计划中,没有办法做到这一点。不管怎样,都要像对待任何局部变量一样对待它。你没做什么坏事。我更新了问题以涵盖递归函数,它们呢?@recp关于递归:没问题,函数的每个调用都会得到它自己的参数副本。函数参数与局部变量完全相同,只是它已在函数开始时初始化。在递归调用中使用函数参数是创建值堆栈的常用方法。因为每个函数调用都会创建一个新的值的堆叠副本,所以一些程序员利用此创建一个伪堆栈,允许对连续递归调用产生的值使用当前值进行操作。@recp递归函数没有区别-参数仍然是本地副本。当然,这里绝对没有必要使用递归,你从递归中得到的唯一东西是:执行速度慢,RAM使用多,可读性差,堆栈溢出的危险。我敢打赌,你说的是a中的某些编译器