我可以内联使用静态变量的函数吗?

5

我有这段代码:

// my.h

#ifndef MY_HEADER
#define MY_HEADER

int create_uid();

#endif

// my.cpp

#include "my.h"

static int _next_uid = 0;

int create_uid()
{
    return _next_uid++;
}

我希望内联create_uid()函数,同时保持_next_uid变量对程序全局可见,以便使变量唯一。

我的问题是:

  1. 我可以这样做吗?
  2. inline语句是否需要将_next_uid变量在编译单元外可见?

注意:此处的解答似乎没有明确回答这些问题。


4
使用C++17,你可以使用静态内联变量(static inline variables)(https://dev59.com/xFoT5IYBdhLWcg3w6isA)来实现这一点。 - wohlstad
@wohlstad 相关:https://dev59.com/BlMH5IYBdhLWcg3w0jiF - Daniel Langr
1
与问题无关:_next_uid是全局命名空间范围内的保留标识符,因为它以下划线开头。您不允许在那里声明它。 - user17732522
“inline”函数是什么意思?C++关键字“inline”表示同一个函数可以在不同的翻译单元中有多个定义。就这样。是的,它曾经是编译器扩展函数的提示,但编译器比你和我更擅长确定何时适当地进行扩展。 - Pete Becker
@PeteBecker 这些问题不是相关的吗?如果没有使用 inline,那个函数只能在一个翻译单元中定义,这意味着编译器无法将其代码内联到其他翻译单元中。 - Daniel Langr
1
@DanielLangr -- 我还没有深入研究过,但通常的咒语是“全程序优化”和“链接器优化”。假设地说,编译系统可以捕捉到该函数的定义并将其插入多个位置,这是没有问题的。 - Pete Becker
3个回答

3
如果你只想要一个单独的_next_uid,那么只需将以下内容放入您的头文件中:
inline int create_uid()
{
  static int _next_uid = 0;
  return _next_uid++;
}

谢谢,但我不想有单独的 _next_uid 副本。 - Narann
@Narann,为什么你写了:“同时将 _next_uid 变量保持为 静态的”?这听起来像一个 X-Y 问题。你应该明确指定你正在尝试解决什么问题。 - Daniel Langr
谢谢,我更新了问题以更清楚地表明我希望_next_uid成为整个程序/库的唯一ID。一种全局变量。编辑:看起来您的编辑实际上是有效的! - Narann

2

简短回答。不行。以下代码

// my.h

static int _next_uid = 0;

inline int create_uid() {
    return _next_uid++;
}

这段代码可能会编译成功,但如果在多个翻译单元中使用,则会导致未定义的行为。这是因为不同翻译单元中的_next_uid变量是不同的实体。因此create_uid()的定义也是不同的。然而:

如果一个具有外部链接的内联函数在不同的翻译单元中有不同的定义,则其行为是未定义的。[1]

相反,您可以在函数中使用本地作用域静态变量,就像@DanielLangr在其他答案中所示。这样做的缺点是该变量不能在函数外部访问。或者,正如@wohlstad在其中一条评论中提到的那样,您可以使用C++17内联变量。

// my.h
inline int _next_uid = 0;

inline int create_uid() {
    return _next_uid++;
}

请注意,这并不定义静态变量。使用 staticinline 将产生与仅使用 static 相同的效果 [3],这会导致我上面提到的未定义行为。
内联函数的意思是,所有它使用的变量必须从它所在的翻译单元中可达。这对于唯一的静态变量(因此对其他TU不可见)是行不通的。
[1]: https://en.cppreference.com/w/cpp/language/inline
[2]: https://dev59.com/ysTra4cB1Zd3GeqP31Kn#72124623
[3]: https://dev59.com/BlMH5IYBdhLWcg3w0jiF#58101307

-1

摘要:

如果你将inline next_id()的实现放在单个c文件中,即该函数在单个编译单元中,则它将无法工作。因此,main找不到inline next_id(),会出现undefined reference错误。

如果你在一个共享头文件中声明inline next_id(),则可以编译它,这样每个编译单元都能正确找到inline next_id()

在我的情况下,在进程的虚拟地址空间的.DATA段中,只有一个全局变量的实例。输出的数字是连续的。

示例:

Makefile 8:

all:
    c++ -c main.cpp
    c++ -c second.cpp
    c++ -c share.cpp
    c++ main.o second.o share.o -o main

clean:
    rm -f main.o second.o share.o main

main.cpp 12:

#include <cstdio>
#include "share.hpp"
#include "second.hpp"

int main(){
    printf("[main] %d\n", next_id());
    consume_id();
    printf("[main] %d\n", next_id());
    consume_id();
    printf("[main] %d\n", next_id());
    return 0;
}

second.hpp 1:

void consume_id();

second.cpp 7:

#include <cstdio>

#include "share.hpp"

void consume_id(){
    printf("[scnd] %d\n", next_id());
}

share.hpp 4:

#pragma once

int next_id();

share.cpp 7:


static int _next_id = 0;

int next_id()
{
    return _next_id++;
}

结果输出:

[main] 0
[scnd] 1
[main] 2
[scnd] 3
[main] 4

但如果它被改成:

share.cpp 4:

inline int next_id()
{
    return _next_id++;
}

未定义对 `next_id()' 的引用

如果更改为

share.hpp 7:

#pragma once
static int _next_id = 0;

inline int next_id()
{
    return _next_id++;
}

工作

编辑

似乎是未定义的行为

我正在使用 `gcc 版本 11.2.0(Ubuntu 11.2.0-19ubuntu1)

在我的情况下

您将拥有 static int _next_id 的副本,但仅在对象文件中。在内存中只有一个。

objdump -d main > main.s

main.s 143:

00000000000011b3 <_Z7next_idv>:
    11b3:   f3 0f 1e fa             endbr64 
    11b7:   55                      push   %rbp
    11b8:   48 89 e5                mov    %rsp,%rbp
    11bb:   8b 05 53 2e 00 00       mov    0x2e53(%rip),%eax        # 4014 <_ZL8_next_id>
    11c1:   8d 50 01                lea    0x1(%rax),%edx
    11c4:   89 15 4a 2e 00 00       mov    %edx,0x2e4a(%rip)        # 4014 <_ZL8_next_id>
    11ca:   5d                      pop    %rbp
    11cb:   c3                      ret    

这里的函数_Z7next_idv只在内存中出现了1次。

main.s 147:

    11bb:   8b 05 53 2e 00 00       mov    0x2e53(%rip),%eax        # 4014 <_ZL8_next_id>

_next_id 的标签是 _ZL8_next_id,在内存中也只出现了1次。


谢谢,它编译成功了,但在我的测试中,当我打印create_uid()函数中的_next_id变量时,它多次打印相同的值。我怀疑将static int _next_id = 0;放在头文件中会在每个编译单元中重复定义它。 - Narann
1
@Narann "static (...) inside the header duplicate it in each compile unit" 这正是变量上的 static 所要做的事情,即启用内部链接。https://dev59.com/VWUp5IYBdhLWcg3wf3us?rq=1 - R2RT
1
很确定这是未定义行为。在多个翻译单元中包含具有相同名称的对象违反了ODR规则。行为是未定义的,编译器(链接器)不需要对其进行诊断。看到它“工作”只是未定义行为的许多可能表现之一。 - Jakob Stark
1
如果我没记错的话,ODR并不关心“相同的名称”,它关注的是相同的实体。对于内部链接(静态),我们在每个翻译单元中都有一个单独的实体。 - Daniel Langr
2
@DanielLangr 你可能是对的,但我认为它仍然是未定义行为(UB),但原因略有不同。不是因为静态变量(具有内部链接),而是因为内联函数(具有外部链接)。内联函数要求在所有翻译单元中具有相同定义。现在,由于函数声明引用了不同的静态_next_id实体,它们不再相同。无论如何,如果我使用或不使用优化编译上面的程序,我会得到不同的结果。这通常是UB的指示。 - Jakob Stark
1
是的,这显然是一种ODR违规,特别是违反了链接cppreference页面上给出的“在每个定义内部进行名称查找会发现相同的实体(经过重载分辨率),除了[...]”的要求。所谓能够工作的程序是不合法的,无需诊断。 - user17732522

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接