Bagaimana cara kerja variabel sebaris?

124

Pada pertemuan Oulu ISO C ++ Standards 2016, sebuah proposal yang disebut Variabel Inline dipilih menjadi C ++ 17 oleh komite standar.

Dalam istilah awam, apa itu variabel sebaris, bagaimana cara kerjanya dan untuk apa mereka berguna? Bagaimana variabel sebaris harus dideklarasikan, ditentukan dan digunakan?

jotik
sumber
@jotik Saya kira operasi yang setara akan menggantikan setiap kejadian variabel dengan nilainya. Biasanya ini hanya valid jika variabelnya adalah const.
melpomene
5
Itu bukan satu-satunya hal yang dilakukan inlinekata kunci untuk fungsi. Kata inlinekunci, bila diterapkan ke fungsi, memiliki satu efek penting lainnya, yang diterjemahkan langsung ke variabel. Sebuah inlinefungsi, yang mungkin dideklarasikan dalam file header, tidak akan menghasilkan kesalahan "simbol duplikat" pada waktu penautan, meskipun header tersebut #included oleh beberapa unit terjemahan. Kata inlinekunci, bila diterapkan ke variabel, akan memiliki hasil yang persis sama. Tamat.
Sam Varshavchik
4
^ Dalam arti 'mengganti panggilan apa pun ke fungsi ini dengan salinan kodenya di tempat', inlinehanya permintaan yang lemah dan tidak mengikat ke pengoptimal. Penyusun bebas untuk tidak menyebariskan fungsi yang diminta dan / atau ke sebaris yang tidak Anda beri anotasi. Sebaliknya, tujuan sebenarnya dari inlinekata kunci tersebut adalah untuk menghindari kesalahan definisi ganda.
underscore_d

Jawaban:

121

Kalimat pertama proposal:

The inlinespecifier dapat diterapkan untuk variabel serta fungsi.

Efek ¹ dijamin inlineseperti yang diterapkan ke suatu fungsi, adalah memungkinkan fungsi didefinisikan secara identik, dengan tautan eksternal, dalam beberapa unit terjemahan. Untuk in-practice artinya mendefinisikan fungsi dalam sebuah header, yang dapat dimasukkan ke dalam beberapa unit terjemahan. Proposal memperluas kemungkinan ini ke variabel.

Jadi, dalam istilah praktis proposal (sekarang diterima) memungkinkan Anda menggunakan inlinekata kunci untuk menentukan constvariabel cakupan namespace tautan eksternal , atau staticanggota data kelas apa pun , dalam file header, sehingga beberapa definisi yang dihasilkan saat header itu disertakan dalam beberapa unit terjemahan tidak masalah dengan penaut - ini hanya memilih salah satunya.

Hingga dan termasuk C ++ 14 mesin internal untuk ini telah ada, untuk mendukung staticvariabel dalam templat kelas, tetapi tidak ada cara yang nyaman untuk menggunakan mesin itu. Seseorang harus menggunakan trik seperti

template< class Dummy >
struct Kath_
{
    static std::string const hi;
};

template< class Dummy >
std::string const Kath_<Dummy>::hi = "Zzzzz...";

using Kath = Kath_<void>;    // Allows you to write `Kath::hi`.

Dari C ++ 17 dan seterusnya saya percaya seseorang dapat menulis dengan adil

struct Kath
{
    static std::string const hi;
};

inline std::string const Kath::hi = "Zzzzz...";    // Simpler!

… Di file header.

Proposal mencakup kata-kata

Seorang anggota data yang inline statis dapat didefinisikan dalam definisi kelas dan mungkin s pecify penjepit-atau-sama-initializer. Jika anggota dideklarasikan dengan constexprpenentu, itu dapat dideklarasikan kembali dalam lingkup namespace tanpa penginisialisasi (penggunaan ini tidak digunakan lagi; lihat DX). Deklarasi anggota data statis lainnya tidak boleh menetapkan penguat-atau-pengenal-setara

… Yang memungkinkan hal di atas lebih disederhanakan menjadi adil

struct Kath
{
    static inline std::string const hi = "Zzzzz...";    // Simplest!
};

… Sebagaimana dicatat oleh TC dalam komentar untuk jawaban ini.

Selain itu,  ​constexprpenentu menyiratkan  inline untuk anggota data statis serta fungsi.


Catatan:
¹ Karena suatu fungsi inlinejuga memiliki efek petunjuk tentang pengoptimalan, kompilator harus memilih untuk mengganti panggilan fungsi ini dengan substitusi langsung dari kode mesin fungsi tersebut. Petunjuk ini bisa diabaikan.

Cheers and hth. - Alf
sumber
2
Selain itu, pembatasan const hanya berlaku untuk variabel cakupan namespace. Class-scope yang (seperti Kath::hi) tidak harus berupa const.
TC
4
Laporan yang lebih baru menunjukkan bahwa constpembatasan dihapus seluruhnya.
TC
2
@ Nick: Karena Richard Smith ("editor proyek" komite C ++ saat ini) adalah salah satu dari dua penulis, dan karena dia adalah "pemilik kode frontend Clang C ++", tebak Clang. Dan konstruksi disusun dengan dentang 3.9.0 di atas Godbolt . Ini memperingatkan bahwa variabel sebaris adalah ekstensi C ++ 1z. Saya tidak menemukan cara untuk membagikan pilihan dan opsi sumber dan penyusun, jadi tautannya hanya ke situs secara umum, maaf.
Cheers and hth. - Alf
1
mengapa perlu kata kunci inline di dalam deklarasi class / struct? Mengapa tidak mengizinkan saja static std::string const hi = "Zzzzz...";?
sasha.sochka
2
@EmilianCioca: Tidak, Anda akan bertabrakan dengan kegagalan pesanan inisialisasi statis . Singleton pada dasarnya adalah alat untuk menghindarinya.
Cheers and hth. - Alf
15

Variabel sebaris sangat mirip dengan fungsi sebaris. Ini memberi sinyal kepada linker bahwa hanya satu instance dari variabel yang harus ada, meskipun variabel tersebut terlihat di beberapa unit kompilasi. Linker perlu memastikan bahwa tidak ada lagi salinan yang dibuat.

Variabel sebaris dapat digunakan untuk menentukan global di perpustakaan hanya header. Sebelum C ++ 17, mereka harus menggunakan solusi (fungsi inline atau peretasan template).

Misalnya, salah satu solusinya adalah menggunakan singleton Meyer dengan fungsi inline:

inline T& instance()
{
  static T global;
  return global;
}

Ada beberapa kelemahan dengan pendekatan ini, sebagian besar dalam hal kinerja. Overhead ini dapat dihindari dengan solusi template, tetapi mudah untuk salah.

Dengan variabel sebaris, Anda dapat langsung mendeklarasikannya (tanpa mendapatkan kesalahan linker definisi ganda):

inline T global;

Selain pustaka hanya header, ada kasus lain di mana variabel sebaris dapat membantu. Nir Friedman membahas topik ini dalam ceramahnya di CppCon: Apa yang harus diketahui developer C ++ tentang global (dan linker) . Bagian tentang variabel sebaris dan penyelesaiannya dimulai pada 18m9s .

Singkat cerita, jika Anda perlu mendeklarasikan variabel global yang dibagikan di antara unit kompilasi, mendeklarasikannya sebagai variabel sebaris di file header sangatlah mudah dan menghindari masalah dengan solusi pra-C ++ 17.

(Masih ada kasus penggunaan untuk singleton Meyer, misalnya, jika Anda secara eksplisit ingin memiliki inisialisasi malas.)

Philipp Claßen
sumber
11

Contoh minimal runnable

Fitur C ++ 17 yang luar biasa ini memungkinkan kita untuk:

main.cpp

#include <cassert>

#include "notmain.hpp"

int main() {
    // Both files see the same memory address.
    assert(&notmain_i == notmain_func());
    assert(notmain_i == 42);
}

notmain.hpp

#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP

inline constexpr int notmain_i = 42;

const int* notmain_func();

#endif

notmain.cpp

#include "notmain.hpp"

const int* notmain_func() {
    return &notmain_i;
}

Kompilasi dan jalankan:

g++ -c -o notmain.o -std=c++17 -Wall -Wextra -pedantic notmain.cpp
g++ -c -o main.o -std=c++17 -Wall -Wextra -pedantic main.cpp
g++ -o main -std=c++17 -Wall -Wextra -pedantic main.o notmain.o
./main

GitHub upstream .

Lihat juga: Bagaimana cara kerja variabel sebaris?

Standar C ++ pada variabel sebaris

Standar C ++ menjamin bahwa alamatnya akan sama. C ++ 17 N4659 standard draft 10.1.6 "The inline specifier":

6 Fungsi atau variabel sebaris dengan hubungan eksternal harus memiliki alamat yang sama di semua unit terjemahan.

cppreference https://en.cppreference.com/w/cpp/language/inline menjelaskan bahwa jika statictidak diberikan, maka ia memiliki tautan eksternal.

Penerapan variabel sebaris GCC

Kita dapat mengamati bagaimana ini diterapkan dengan:

nm main.o notmain.o

yang mengandung:

main.o:
                 U _GLOBAL_OFFSET_TABLE_
                 U _Z12notmain_funcv
0000000000000028 r _ZZ4mainE19__PRETTY_FUNCTION__
                 U __assert_fail
0000000000000000 T main
0000000000000000 u notmain_i

notmain.o:
0000000000000000 T _Z12notmain_funcv
0000000000000000 u notmain_i

dan man nmmengatakan tentang u:

"u" Simbolnya adalah simbol global yang unik. Ini adalah ekstensi GNU untuk set standar binding simbol ELF. Untuk simbol seperti itu, dynamic linker akan memastikan bahwa dalam keseluruhan proses hanya ada satu simbol dengan nama dan tipe ini yang digunakan.

jadi kami melihat bahwa ada ekstensi ELF khusus untuk ini.

Pra-C ++ 17: extern const

Sebelum C ++ 17, dan di C, kita dapat mencapai efek yang sangat mirip dengan sebuah extern const, yang akan mengarah ke satu lokasi memori yang digunakan.

Kerugiannya inlineadalah:

  • tidak mungkin membuat variabel constexprdengan teknik ini, hanya inlinememungkinkan: Bagaimana cara mendeklarasikan constexpr extern?
  • ini kurang elegan karena Anda harus mendeklarasikan dan mendefinisikan variabel secara terpisah di file header dan cpp

main.cpp

#include <cassert>

#include "notmain.hpp"

int main() {
    // Both files see the same memory address.
    assert(&notmain_i == notmain_func());
    assert(notmain_i == 42);
}

notmain.cpp

#include "notmain.hpp"

const int notmain_i = 42;

const int* notmain_func() {
    return &notmain_i;
}

notmain.hpp

#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP

extern const int notmain_i;

const int* notmain_func();

#endif

GitHub upstream .

Alternatif header saja Pre-C ++ 17

Ini tidak sebaik externsolusinya, tetapi berfungsi dan hanya menggunakan satu lokasi memori:

Sebuah constexprfungsi, karena constexprmenyiratkaninline dan inline memungkinkan (memaksa) definisi tersebut muncul di setiap unit terjemahan :

constexpr int shared_inline_constexpr() { return 42; }

dan saya yakin bahwa setiap kompilator yang layak akan melakukan panggilan inline.

Anda juga dapat menggunakan variabel integer constatau constexprstatis seperti di:

#include <iostream>

struct MyClass {
    static constexpr int i = 42;
};

int main() {
    std::cout << MyClass::i << std::endl;
    // undefined reference to `MyClass::i'
    //std::cout << &MyClass::i << std::endl;
}

tetapi Anda tidak dapat melakukan hal-hal seperti mengambil alamatnya, atau menjadi digunakan odr, lihat juga: https://en.cppreference.com/w/cpp/language/static "Anggota statis konstan" dan Menentukan data statis constexpr anggota

C

Dalam C situasinya sama dengan C ++ sebelum C ++ 17, saya telah mengunggah contoh di: Apa artinya "statis" di C?

Satu-satunya perbedaan adalah bahwa dalam C ++, constmenyiratkan staticuntuk GLOBALS, tapi tidak di C: C ++ semantik `const` statis vs` const`

Adakah cara untuk membuatnya sebaris sepenuhnya?

TODO: apakah ada cara untuk menyebariskan variabel sepenuhnya, tanpa menggunakan memori sama sekali?

Mirip seperti yang dilakukan preprocessor.

Ini akan membutuhkan entah bagaimana:

  • melarang atau mendeteksi jika alamat variabel diambil
  • menambahkan informasi tersebut ke file objek ELF, dan biarkan LTO mengoptimalkannya

Terkait:

Diuji di Ubuntu 18.10, GCC 8.2.0.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
sumber
2
inlinehampir tidak ada hubungannya dengan inlining, baik untuk fungsi maupun variabel, terlepas dari kata itu sendiri. inlinetidak memberi tahu kompilator untuk menyebariskan apa pun. Ini memberitahu linker untuk memastikan bahwa hanya ada satu definisi, yang secara tradisional merupakan tugas programmer. Jadi, "Adakah cara untuk menyebarnya sepenuhnya?" setidaknya merupakan pertanyaan yang sama sekali tidak berhubungan.
bukan-pengguna