简体   繁体   English

静态函数如何工作?

[英]How does static work inside a function?

I came across something I haven't seen before. 我遇到了以前从未见过的东西。 I have the following recursive function that only works when i is static 我有以下递归函数,仅当i是静态的时才起作用

void printNthFromLast(Node* head, int n) {

    static int i = 0;

    if(head == nullptr)
        return;

    printNthFromLast(head->next, n);

    if(++i == n)
        cout << head->data;
}

I assume static in this context means shared among multiple recursive calls to the same function printNthFromLast ? 我假设在这种情况下,静态意味着对同一函数 printNthFromLast 多个递归调用之间共享

The real confusing part is that it doesn't reinitialize it to 0 every time the recursive function calls itself. 真正令人困惑的部分是它不会在每次递归函数调用自身时都将其初始化为0。 It's like this whole line of code static int i = 0; 就像整行代码static int i = 0; is skipped? 被跳过?

Can someone explain this to me? 谁可以给我解释一下这个?

The behavior you observed : 您观察到的行为:

The real confusing part is that it doesn't reinitialize it to 0 every time the recursive function calls itself. 真正令人困惑的部分是它不会在每次递归函数调用自身时都将其初始化为0。 It's like this whole line of code static int i = 0; 就像整行代码static int i = 0; is skipped? 被跳过?

is exactly what you are asking for with static. 正是您对static的要求。 A local static variable is initalized the first time its definition (ie, the line static int i = 0; ) is reached. 首次到达局部静态变量的定义时(即行static int i = 0; )将其初始化。

In your case it means it will be set to zero only on the first call ever to this method during the whole runtime of your program. 在您的情况下,这意味着仅在程序整个运行期间第一次对此方法进行调用时,它将设置为零。 There is no notion of multiple recursive calls to the same function , so it will make no difference if the method is invoked by itself (the multiple recursive call you are referring to) or if you are starting a whole new stack of recursion somewhere else in your client code. 没有对同一个函数进行多次递归调用的概念,因此,如果方法本身被调用(您所指的多次递归调用),或者您要在其中的其他地方启动全新的递归堆栈,则不会有任何区别。您的客户代码。

Possible solution 可能的解决方案

To go back on your description, it will only work with i being static (for n!=1) because, if your removed static keyword, then i would be initialized to zero each time the method is entered (a different local instance of i for each invocation of the method). 回到您的描述中,它仅在i为静态时有效 (对于n!= 1),因为如果删除了static关键字,则每次输入该方法时i都会初始化为零(i的另一个本地实例)对于方法的每次调用)。 Thus in your condition 因此,在您的情况下

  if(++i == n)

++i would always evaluate to 1, independently of the recursion depth. ++ i将始终取值为1,与递归深度无关。

If you want the static i to be reinitialized each time you call your method in your client code (ie to start a new stack of recursion), you could write it like : 如果您希望每次在客户端代码中调用方法时都重新初始化static i(即启动新的递归堆栈),则可以这样编写:

void printNthFromLast(Node* head, int n, bool reinit=true)
{

   static int i = 0;

   if(reinit)
   {
       i=0;
   }

   if(head == nullptr)
      return;

   printNthFromLast(head->next, n, false);

   if(++i == n)
       cout << head->data;
}

Thread safe with tail call optimization 线程安全与尾调用优化

A cleaner solution would be to have i as a mutable parameter to the function, so your function would be thread-safe. 较干净的解决方案是将i作为函数的可变参数,因此您的函数将是线程安全的。 And with a better ordering of the operation, you do not need to save the previous invocation frame, thus enjoying tail call optimization offered by most of recent compilers. 而且,通过更好地排序操作,您无需保存以前的调用框架,从而可以享受大多数最新编译器提供的尾部调用优化。

EDIT : As pointed by Matthieu, my original implementation printed the Nth element instead of the Nth from last element. 编辑 :正如Matthieu指出的那样,我的原始实现打印了第N个元素,而不是最后一个元素中的第N个元素。 Fixing while enabling TCO is less elegant, but can be done as follows : 启用TCO时进行修复不太好,但是可以按照以下步骤进行操作:

/// n==1 means printing the last element here
static void printNthFromLastImpl(const Node* currentNode, const Node* offsetNode, const int n, int currentDepth)
{
    // n == 0 will never print in the original example by op, so forbid it
    assert(n);

    if(++currentDepth > n)
    {
        offsetNode = offsetNode->next;
    }

    if(currentNode->next)
    {
        currentNode = currentNode->next;
    }
    else
    {
        if(currentDepth >= n)
        {
            cout << offsetNode->data;
        }
        return;
    }


    return printNthFromLastImpl(currentNode, offsetNode, n, currentDepth);
}

/// \brief : code to call from client
static void printNthFromLast(const Node* head, const int n)
{
    if (head)
    {
        printNthFromLastImpl(head, head, n, 0);
    }
}

When you declare a static local variable the compiler only initializes it once (the first time control flow passes over the variable declaration); 当您声明static局部变量时,编译器仅对其初始化一次(第一次控制流通过变量声明)。 thereafter the variable keeps its value across all calls to the function that declares it for the lifetime of the program, much like a global variable. 此后,变量在整个程序生命周期内对声明该变量的函数的所有调用中保持其值,这与全局变量非常相似。

How is this achieved? 如何实现的?

When the compiler sees something like this: 当编译器看到类似这样的内容时:

void foo() {
    static int i = 0;

    cout << i++;
}

It produces code equivalent to this: 它产生的代码与此等效:

bool foo_i_initialized = false; // global
int foo_i; // global

void foo() {
    if (!foo_i_initialized) {
        foo_i = 0;
        foo_i_initialized = true;
    }

    cout << foo_i++;
}

The above example is a bit contrived because here foo_i is a primitive initialized with a constant and as such it could have been statically initialized in global scope (removing the need for the boolean flag), but this mechanism also handles more complicated scenarios. 上面的示例有些人为,因为foo_i是用常量初始化的原语,因此它可以在全局范围内进行静态初始化(无需使用boolean标志),但是该机制还可以处理更复杂的情况。

The initialization is only executed at the first call of the function. 初始化仅在函数的第一次调用时执行。 Subsequent calls will use the already inititalized value. 后续调用将使用已经初始化的值。

So, as said, static int i = 0; 因此,如上所述, static int i = 0; is local to the function. 对功能而言是本地的。 It is initialized the first time flow-control passes through its definition, and skipped ever after. 第一次在流控制通过其定义时对其进行初始化,此后一直跳过。 Two special cases are made: 有两种特殊情况:

  • in case of dynamic initialization (by a function) which throws an exception, initialization will be reattempted the next time flow-control passes through the definition. 在动态初始化(通过函数)引发异常的情况下,下一次流控制通过定义时,将重新尝试初始化。
  • in case of calls from multiple threads, the first thread does the initialization and all others are blocked until it is done (or fails with an exception) 如果有来自多个线程的调用,则第一个线程进行初始化,而所有其他线程则被阻塞,直到完成为止(否则会失败,并发生异常)

Now, regarding your code: don't. 现在,关于您的代码:不要。 A local static is a global variable in disguise, your code is equivalent to: 局部静态变量是变相的全局变量,您的代码等效于:

int i = 0;

void printNthFromLast(Node* head, int n) {    
    if(head == nullptr)
        return;

    printNthFromLast(head->next, n);

    if(++i == n)
        cout << head->data;
}

Note: not only is it more difficult to debug, it is also not thread-safe. 注意:不仅不仅调试困难,而且不是线程安全的。

Instead, you should provide a local variable for this usage: 相反,您应该为此用法提供一个局部变量:

static void printNthFromLastImpl(Node const* const head, int& i, int const n) {
    if(head == nullptr)
        return;

    printNthFromLastImpl(head->next, i, n);

    if(++i == n)
        cout << head->data;
}

// Each call to this function instantiates a new `i` object.
void printNthFromLast(Node const* const head, int const n) {
    int i = 0;
    printNthFromLastImpl(head, i, n);
}

EDIT: As remarked by Ad N , since the list is not modified it should be passed by const pointer. 编辑:Ad N所述 ,由于列表未修改,因此应通过const指针传递。

Following Ad N latest edit (TCO version), I realized an iterative implementation should work ( note, there might be some off by one errors ). 在Ad N最新编辑(TCO版本)之后,我意识到应该可以执行迭代实现( 请注意,可能会有一些错误 )。

void printNthFromLast(Node const* const head, int const n) {
    if (n == 0) { return; }

    // Set "probe" to point to the nth item.
    Node const* probe = head;
    for (int i = 0; i != n; ++i) {
        if (probe == nullptr) { return; }
        probe = probe->next;
    }

    Node const* current = head;

    // Move current and probe at the same rhythm;
    // when probe reaches the end, current is the nth from the end.
    while (probe) {
        current = current->next;
        probe = probe->next;
    }

    std::cout << current->data;
}

You've described it very well yourself. 您已经很好地描述了它。 A static variable is initialised only once, the first time through the function, and the variable is then shared across calls to the function. 静态变量仅在第一次通过函数初始化一次,然后在对函数的调用之间共享该变量。

A variable declared static is initialized only once. 声明为static的变量仅初始化一次。 So even, when the function is called again, the value of the variable i here in this context is retained from the previous call. 因此,即使再次调用该函数,此处的变量i的值在此上下文中也保留在先前的调用中。
The next time the program reaches the static initialize statement to an initialized variable, it skips the statement. 程序下一次到达静态初始化语句的初始化变量时,将跳过该语句。 Since, static variables are stored in the BSS segment and not on the stack, the previous state of the variable in previous function calls, is not altered. 由于静态变量存储在BSS段中而不是堆栈中,因此不会更改以前函数调用中变量的先前状态。

Not only shared among multiple recursive calls, shared among all calls. 不仅在多个递归调用之间共享,在所有调用之间共享。

There is actually only a single instance of the variable i , and it's shared among all calls of the function. 实际上,变量i仅有一个实例,并且在该函数的所有调用之间共享。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM