5

I have this code:

// 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++;
}

I want to inline create_uid(), while keeping the _next_uid variable global to the program so the variable is unique.

My questions are:

  1. Can I do this?
  2. Is the inline statement require _next_uid to be visible outside the compilation unit?

Note: This doesn't seems to answer those questions clearly.

einpoklum
  • 118,144
  • 57
  • 340
  • 684
Narann
  • 819
  • 2
  • 8
  • 20
  • 4
    With c++17 you can use static _inline variables_ (https://stackoverflow.com/questions/38043442/how-do-inline-variables-work) to achieve this. – wohlstad May 05 '22 at 08:50
  • @wohlstad Related: https://stackoverflow.com/q/58101044/580083 – Daniel Langr May 05 '22 at 09:02
  • 1
    Unrelated to question: `_next_uid` is a reserved identifier in the global namespace scope because it starts with an underscore. You are not allowed to declare it there. – user17732522 May 05 '22 at 12:04
  • What do you mean by "inline" the function? The C++ keyword `inline` means that it's okay to have multiple definitions of the same function in different translation units. That's all. Yes, it used to be a hint to the compiler to expand the function in place where it's used, but compilers are much better at figuring out where that's appropriate than you and I are. – Pete Becker May 05 '22 at 13:07
  • @PeteBecker Aren't those problems related? Without being `inline`, that function would need to be defined in a single translation unit only, which implies that a compiler wouldn't be able to _inline_ its code in the others. – Daniel Langr May 05 '22 at 13:17
  • 1
    @DanielLangr -- I haven't looked into it, but the usual incantations here are "full-program optimization" and "linker optimization". Hypothetically, there's no reason the compilation system couldn't pick up that definition of the function and insert it in multiple places. – Pete Becker May 05 '22 at 13:24

3 Answers3

3

Edited after clarifiation of the question.

If you want only a single _next_uid, then simply put the following into your header file:

inline int create_uid()
{
  static int _next_uid = 0;
  return _next_uid++;
}
Daniel Langr
  • 22,196
  • 3
  • 50
  • 93
  • Thanks, but I don't want to have separate copies of `_next_uid`. – Narann May 05 '22 at 09:11
  • @Narann The, why did you write: _"while keeping the `_next_uid` variable **static**"_? It sounds like an _X-Y problem_. You should specify what problem you are trying to solve. – Daniel Langr May 05 '22 at 09:19
  • Thanks, I have updated the question to make more clear that I want `_next_uid` to be a unique id for the whole program/library. A kinda global variable. EDIT: It seems that your edit actually works! – Narann May 05 '22 at 09:34
2

Short answer. No. The following code

// my.h

static int _next_uid = 0;

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

will probably compile, but will result in undefined behaviour if used in more than one translation units. This is because the _next_uid variables are different entities in different translation units. Thus the definitions of create_uid() are also different. However:

If an inline function [...] with external linkage is defined differently in different translation units, the behavior is undefined. [1]

What you can do instead is either use a local scope static variable in the function, like @DanielLangr showed in one of the other answers [1]. This has the disadvantage, that that variable cannot be accessed outside of the function. Alternatively as @wohlstad mentioned in one of the comments, you can use a C++17 inline variable:

// my.h
inline int _next_uid = 0;

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

Note that this does not define a static variable. Using static and inline will have the same effect as just using static [3], which results in the undefined behaviour I mentioned above.

Inlining a function means per definition, that all the variables it uses must be reachable from the translation unit where it is inlined. This cannot work with a unique static (thus not visible to other TUs) variable.

[1]: https://en.cppreference.com/w/cpp/language/inline
[2]: https://stackoverflow.com/a/72124623/17862371
[3]: https://stackoverflow.com/a/58101307/17862371

Jakob Stark
  • 3,346
  • 6
  • 22
-1

Summary:

It doesn't work if you put implementation of inline next_id() to a single c file, which means the function is in a single compile unit. So main cannot find inline next_id(), you'll get the error undefined reference.

It can be compiled if you declare inline next_id() in a shared header file, in which case each compile unit will properly find inline next_id().

In my case, only one instance of this global variable will appear in virtual address space .DATA segment of the process. The output number is continuous.

Example:

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++;
}

Result output:

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

But if it is changed to:

share.cpp 4:

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

undefined reference to `next_id()'

If changed to

share.hpp 7:

#pragma once
static int _next_id = 0;

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

Works

EDIT:

It seems to be an undefined behavior

I'm using `gcc version 11.2.0 (Ubuntu 11.2.0-19ubuntu1)

IN MY CASE

You will have copies of static int _next_id but only in the object file. In memory there is only one.

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    

Here function _Z7next_idv only appears in memory for 1 time.

main.s 147:

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

The label of _next_id is _ZL8_next_id, only appears in memory for 1 time as well.

Pluveto
  • 456
  • 5
  • 9
  • Thanks, it compile, but in my tests, when I print `_next_id` (in the `create_uid()` function), it prints the same value multiple time. I suspect that putting `static int _next_id = 0;` inside the header duplicate it in each compile unit. – Narann May 05 '22 at 09:29
  • 1
    @Narann "static (...) inside the header duplicate it in each compile unit" That's exactly what `static` on variable is supposed to do, that is to enable internal linkage. https://stackoverflow.com/questions/15235526/the-static-keyword-and-its-various-uses-in-c?rq=1 – R2RT May 05 '22 at 09:36
  • 1
    Pretty sure this is undefined behaviour. Including an object with the same name into multiple translation units violates the [ODR rule](https://en.cppreference.com/w/cpp/language/definition#One_Definition_Rule). The behaviour is undefined and the compiler (linker) is not required to diagnose it. Seeing it "working" is just one of many possible manifestations of undefined behaviour. – Jakob Stark May 05 '22 at 09:54
  • 1
    @JakobStark IIRC, ODR does not care about _"the same name"_. It cares about the same entity. With internal linkage (static), we have a separate entity in each TU. – Daniel Langr May 05 '22 at 10:28
  • 2
    @DanielLangr You are probably right about that but think it is UB nevertheless (but because of a slightly different reason). Not because of the static variable (that has internal linkage) but because of the inline function (that has external linkage). Inline functions are required to have the **same** definition in all translation units. Now because the function declarations refer to different static `_next_id` entities, they are not the same anymore. Anyway, I get different results if I compile the above program with and without optimization. This is usually an indication of UB. – Jakob Stark May 05 '22 at 11:15
  • 1
    Yes, clearly an ODR violation, specifically violating the requirement "_name lookup from within each definition finds the same entities (after overload-resolution), except that [...]_" given on the linked cppreference page. The program that supposedly works is ill-formed, no diagnostic required. – user17732522 May 05 '22 at 12:10