develop/languages/cpp/index.html · develop/languages/cpp/index.rst 공식 원문 ↗

Zephyr 3.7.0 · Develop · Languages · C++

C++ 언어 지원

Zephyr C++ application의 지원 범위부터 C++20-compatible header 작성 규칙까지 설명합니다.

Official pathdevelop/languages/cpp/index.html
Source filedevelop/languages/cpp/index.rst
Source versionZephyr 3.7.0
TranslationFull · reviewed

본문 기준: Zephyr 3.7.0 LTS3 · 이후 유지보수 패치는 별도입니다.

Part 1

요약·해설

C++11 기본 설정과 runtime feature, minimal·full library 선택, driver 초기화 제약, C/C++ header test, designated initializer와 anonymous union portability 규칙을 다룹니다.

Runtime

C++11 · Exceptions · RTTI

Library

Minimal · STL · libstdc++

Header

C++20 · Union · CI

Part 2

접을 수 있는 영어 원문 전체

영어 원문 전체 펼치기
원문 SHA-256 152706BC57C9017AA5629174D62E78C69BA0C42F7087E2811559763EB7DA4C5A
.. _language_cpp:

C++ Language Support
####################

C++ is a general-purpose object-oriented programming language that is based on
the C language.

Enabling C++ Support
********************

Zephyr supports applications written in both C and C++. However, to use C++ in
an application you must configure Zephyr to include C++ support by selecting
the :kconfig:option:`CONFIG_CPP` in the application configuration file.

To enable C++ support, the compiler toolchain must also include a C++ compiler
and the included compiler must be supported by the Zephyr build system. The
:ref:`toolchain_zephyr_sdk`, which includes the GNU C++ Compiler (part of GCC),
is supported by Zephyr, and the features and their availability documented
here assume the use of the Zephyr SDK.

The default C++ standard level (i.e. the language enforced by the
compiler flags passed) for Zephyr apps is C++11.  Other standards are
available via kconfig choice, for example
:kconfig:option:`CONFIG_STD_CPP98`.  The oldest standard supported and
tested in Zephyr is C++98.

When compiling a source file, the build system selects the C++ compiler based
on the suffix (extension) of the files. Files identified with either a **cpp**
or a **cxx** suffix are compiled using the C++ compiler. For example,
:file:`myCplusplusApp.cpp` is compiled using C++.

The C++ standard requires the ``main()`` function to have the return type of
``int``. Your ``main()`` must be defined as ``int main(void)``. Zephyr ignores
the return value from main, so applications should not return status
information and should, instead, return zero.

.. note::
    Do not use C++ for kernel, driver, or system initialization code.

Language Features
*****************

Zephyr currently provides only a subset of C++ functionality. The following
features are *not* supported:

* Static global object destruction
* OS-specific C++ standard library classes (e.g. ``std::thread``,
  ``std::mutex``)

While not an exhaustive list, support for the following functionality is
included:

* Inheritance
* Virtual functions
* Virtual tables
* Static global object constructors
* Dynamic object management with the **new** and **delete** operators
* Exceptions
* :abbr:`RTTI (runtime type information)`
* Standard Template Library (STL)

Static global object constructors are initialized after the drivers are
initialized but before the application :c:func:`main()` function. Therefore,
use of C++ is restricted to application code.

In order to make use of the C++ exceptions, the
:kconfig:option:`CONFIG_CPP_EXCEPTIONS` must be selected in the application
configuration file.

Zephyr Minimal C++ Library
**************************

Zephyr minimal C++ library (:file:`lib/cpp/minimal`) provides a minimal subset
of the C++ standard library and application binary interface (ABI) functions to
enable basic C++ language support. This includes:

* ``new`` and ``delete`` operators
* virtual function stub and vtables
* static global initializers for global constructors

The scope of the minimal C++ library is strictly limited to providing the basic
C++ language support, and it does not implement any `Standard Template Library
(STL)`_ classes and functions. For this reason, it is only suitable for use in
the applications that implement their own (non-standard) class library and do
not rely on the Standard Template Library (STL) components.

Any application that makes use of the Standard Template Library (STL)
components, such as ``std::string`` and ``std::vector``, must enable the C++
standard library support.

C++ Standard Library
********************

The `C++ Standard Library`_ is a collection of classes and functions that are
part of the ISO C++ standard (``std`` namespace).

Zephyr does not include any C++ standard library implementation in source code
form. Instead, it allows configuring the build system to link against the
pre-built C++ standard library included in the C++ compiler toolchain.

To enable C++ standard library, select an applicable toolchain-specific C++
standard library type from the :kconfig:option:`CONFIG_LIBCPP_IMPLEMENTATION`
in the application configuration file.

For instance, when building with the :ref:`toolchain_zephyr_sdk`, the build
system can be configured to link against the GNU C++ Library (``libstdc++.a``),
which is a fully featured C++ standard library that provides all features
required by the ISO C++ standard including the Standard Template Library (STL),
by selecting :kconfig:option:`CONFIG_GLIBCXX_LIBCPP` in the application
configuration file.

The following C++ standard libraries are supported by Zephyr:

* GNU C++ Library (:kconfig:option:`CONFIG_GLIBCXX_LIBCPP`)
* ARC MetaWare C++ Library (:kconfig:option:`CONFIG_ARCMWDT_LIBCPP`)

A Zephyr subsystem that requires the features from the full C++ standard
library can select, from its config,
:kconfig:option:`CONFIG_REQUIRES_FULL_LIBCPP`, which automatically selects a
compatible C++ standard library unless the Kconfig symbol for a specific C++
standard library is selected.

Header files and incompatibilities between C and C++
****************************************************

To interact with each other, C and C++ must share code through header
files: data structures, macros, static functions,...  While C and C++
have a large overlap, they're different languages with `known
incompatibilities`_. C is not just a C++ subset. Standard levels (e.g.:
"C+11") add another level of complexity as new features are often
inspired by and copied from the other language but many years later and
with subtle differences. Making things more complex, compilers often
offer early prototypes of features before they become
standardized. Standards can have ambiguities interpreted differently by
different compilers. Compilers can have bugs and these may need
workarounds. To help with this, many projects restrict themselves to a
limited number of toolchains. Zephyr does not.

These compatibility issues affect header files dis-proportionally.  Not
just because they have to be compatible between C and C++, but also
because they end up being compiled in a surprisingly high number of other
source files due to *indirect* inclusion and the `lack of structure and
headers organization`_ that is typical in real-world projects. So, header
files are exposed to a much larger variety of toolchains and project
configurations.
Adding more constraints, the Zephyr project has demanding policies
with respect to code style, compiler warnings, static analyzers and
standard compliance (e.g.: MISRA).

Put together, all these constraints can make writing header files very
challenging. The purpose of this section is to document some best "header
practices" and lessons learned in a Zephyr-specific context. While a lot
of the information here is not Zephyr-specific, this section is not a
substitute for knowledge of C/C++ standards, textbooks and other
references.

Testing
-------

Fortunately, the Zephyr project has an extensive test and CI
infrastructure that provides coverage baselines, catches issues early,
enforces policies and maintains such combinatorial explosions under some
control. The ``tests/lib/cpp/cxx/`` are very useful in this context
because their ``testcase.yaml`` configuration lets ``twister`` iterate
quickly over a range of ``-std`` parameters: ``-std=c++98``,
``-std=c++11``, etc.

Keep in mind unused macros are not compiled.

Designated initializers
-----------------------

Initialization macros are common in header files as they help reduce
boilerplate code. C99 added initialization of ``struct`` and ``union``
types by "designated" member names instead of a list of *bare*
expressions. Some GCC versions support designated initializers even in
their C90 mode.

When used at a simple level, designated initializers are less
error-prone, more readable and more flexible. On the other hand, C99
allowed a surprisingly large and lax set of possibilities: designated
initializers can be out of order, duplicated, "nested" (``.a.x =``),
various other braces can be omitted, designated initializers and not can
be mixed, etc.

Twenty years later, C++20 added designated initializers to C++ but in
much more restricted way; partly because a C++ ``struct`` is actually a
``class``. As described in the C++ proposal number P0329 (which compares
with C) or in any complete C++ reference, a mix is not allowed and
initializers must be in order (gaps are allowed).

Interestingly, the new restrictions in C++20 can cause ``gcc -std=c++20``
to fail to compile code that successfully compiles with
``gcc -std=c++17``. For example, ``gcc -std=c++17`` and older allow the
C-style mix of initializers and bare expressions. This fails to compile
with using ``gcc -std=c++20`` *with the same GCC version*.

Recommendation: to maximize compatibility across different C and C++
toolchains and standards, designated initializers in Zephyr header files
should follow all C++20 rules and restrictions. Non-designated, pre-C99
initialization offers more compatibility and is also allowed but
designated initialization is the more readable and preferred code
style. In any case, both styles must never be mixed in the same
initializer.

Warning: successful compilation is not the end of the incompatibility
story. For instance, the *evaluation order* of initializer expressions is
unspecified in C99! It is the (expected) left-to-right order in
C++20. Other standard revisions may vary. In doubt, do not rely on
evaluation order (here and elsewhere).

Anonymous unions
----------------

Anonymous unions (a.k.a. "unnamed" unions) seem to have been part of C++
from its very beginning. They were not officially added to C until C11.
As usual, there are differences between C and C++. For instance, C
supports anonymous unions only as a member of an enclosing ``struct`` or
``union``, empty lists ``{ }`` have always been allowed in C++ but they
require C23, etc.

When initializing anonymous members, the expression can be enclosed in
braces or not. It can be either designated or bare. For maximum
portability, when initializing *anonymous unions*:

- Do *not* enclose *designated* initializers with braces. This is
  required by C++20 and above which perceive such braces as mixing bare
  expressions with (other) designated initializers and fails to compile.

- Do enclose *bare* expressions with braces. This is required by C.
  Maybe because C is laxer and allows many initialization possibilities
  and variations, so it may need such braces to disambiguate? Note C
  does allow omitting most braces in initializer expressions - but not
  in this particular case of initializing anonymous unions with bare
  expressions.

Some pre-C11 GCC versions support some form of anonymous unions. They
unfortunately require enclosing their designated initializers with
braces which conflicts with this recommendation. This can be solved
with an ``#ifdef __STDC_VERSION__`` as demonstrated in Zephyr commit
`c15f029a7108
<https://github.com/zephyrproject-rtos/zephyr/commit/c15f029a7108>`_ and
the corresponding code review.


.. _`C++ Standard Library`: https://en.wikipedia.org/wiki/C%2B%2B_Standard_Library
.. _`Standard Template Library (STL)`: https://en.wikipedia.org/wiki/Standard_Template_Library
.. _`known incompatibilities`: https://en.wikipedia.org/wiki/Compatibility_of_C_and_C%2B%2B
..  _`lack of structure and headers organization`:
    https://github.com/zephyrproject-rtos/zephyr/issues/41543
.. _`gcc commit [C++ PATCH] P0329R4: Designated Initialization`:
    https://gcc.gnu.org/pipermail/gcc-patches/2017-November/487584.html

Part 3

한국어 전문 번역

C++ 언어 지원

C++는 C 언어를 기반으로 한 범용 object-oriented programming language입니다.

C++ 지원 활성화

Zephyr는 C와 C++로 작성한 application을 모두 지원합니다. Application에서 C++를 사용하려면 application configuration file에서 CONFIG_CPP를 선택해 Zephyr에 C++ 지원을 포함해야 합니다.

Compiler toolchain에는 C++ compiler가 포함되어야 하고 그 compiler를 Zephyr build system이 지원해야 합니다. GNU C++ Compiler(GCC의 일부)를 포함한 Zephyr SDK를 Zephyr가 지원하며, 이 문서의 feature와 availability는 Zephyr SDK 사용을 전제로 합니다.

Zephyr application에 적용되는 기본 C++ standard level, 즉 compiler flag가 강제하는 언어 level은 C++11입니다. CONFIG_STD_CPP98 같은 Kconfig choice로 다른 standard를 선택할 수 있습니다. Zephyr가 지원하고 test하는 가장 오래된 standard는 C++98입니다.

Source file을 compile할 때 build system은 suffix(extension)에 따라 C++ compiler를 선택합니다. cpp 또는 cxx suffix인 file은 C++ compiler로 compile합니다. 예를 들어 myCplusplusApp.cpp는 C++로 compile됩니다.

C++ standard에 따라 main() function의 return type은 int여야 하며 int main(void)로 정의해야 합니다. Zephyr는 main의 return value를 무시하므로 application은 status 정보를 return하지 말고 0을 return해야 합니다.

언어 feature

현재 Zephyr는 C++ 기능의 일부만 제공합니다. 다음 기능은 지원하지 않습니다.

  • Static global object destruction
  • OS-specific C++ standard library class(예: std::thread, std::mutex)

완전한 목록은 아니지만 다음 기능을 지원합니다.

  • Inheritance
  • Virtual function
  • Virtual table
  • Static global object constructor
  • new와 delete operator를 통한 dynamic object 관리
  • Exception
  • RTTI(runtime type information)
  • Standard Template Library(STL)

Static global object constructor는 driver initialization이 끝난 뒤, application main() function을 실행하기 전에 초기화됩니다. 따라서 C++ 사용은 application code로 제한됩니다.

C++ exception을 사용하려면 application configuration file에서 CONFIG_CPP_EXCEPTIONS를 선택해야 합니다.

Zephyr Minimal C++ Library

Zephyr minimal C++ library(lib/cpp/minimal)는 기본 C++ 언어 지원에 필요한 C++ standard library와 application binary interface(ABI) function의 최소 subset을 제공합니다. 포함되는 항목:

  • new와 delete operator
  • Virtual function stub과 vtable
  • Global constructor용 static global initializer

Minimal C++ library의 범위는 기본 C++ 언어 지원으로 엄격히 제한되며 Standard Template Library(STL) class나 function을 구현하지 않습니다. 따라서 자체적인 non-standard class library를 구현하고 STL component에 의존하지 않는 application에만 적합합니다.

std::string과 std::vector 같은 STL component를 사용하는 application은 C++ standard library 지원을 활성화해야 합니다.

C++ Standard Library

C++ Standard Library는 ISO C++ standard의 일부인 std namespace class와 function 모음입니다.

Zephyr는 C++ standard library 구현을 source code 형태로 포함하지 않습니다. 대신 C++ compiler toolchain에 포함된 pre-built C++ standard library와 link하도록 build system을 구성할 수 있습니다.

C++ standard library를 활성화하려면 application configuration file의 CONFIG_LIBCPP_IMPLEMENTATION에서 해당 toolchain용 C++ standard library type을 선택합니다.

Zephyr SDK로 build할 때는 CONFIG_GLIBCXX_LIBCPP를 선택해 GNU C++ Library(libstdc++.a)와 link할 수 있습니다. 이 library는 STL을 포함해 ISO C++ standard가 요구하는 모든 기능을 제공하는 완전한 C++ standard library입니다.

Zephyr가 지원하는 C++ standard library:

  • GNU C++ Library(CONFIG_GLIBCXX_LIBCPP)
  • ARC MetaWare C++ Library(CONFIG_ARCMWDT_LIBCPP)

Full C++ standard library 기능이 필요한 Zephyr subsystem은 자체 config에서 CONFIG_REQUIRES_FULL_LIBCPP를 선택할 수 있습니다. 특정 C++ standard library의 Kconfig symbol이 이미 선택되지 않았다면 compatible library를 자동 선택합니다.

C와 C++ 사이 header file과 비호환성

C와 C++가 상호 작용하려면 data structure, macro, static function 등을 header file로 공유해야 합니다. 두 언어가 크게 겹치지만 알려진 비호환성이 있으며 C는 단순한 C++ subset이 아닙니다. C++11 같은 standard level도 복잡성을 더합니다. 새 기능이 다른 언어에서 영감을 받아 여러 해 뒤 미묘한 차이를 가진 채 도입되기도 하고, compiler가 standard 확정 전 prototype을 제공하기도 합니다. Standard의 모호성을 compiler마다 다르게 해석하거나 compiler bug에 workaround가 필요할 수도 있습니다. 많은 project가 toolchain 수를 제한해 이 문제를 줄이지만 Zephyr는 그렇게 제한하지 않습니다.

이 compatibility 문제는 header file에 특히 큰 영향을 줍니다. C와 C++ 양쪽에 compatible해야 할 뿐 아니라 간접 include와 실제 project에서 흔한 구조·header 정리 부족 때문에 예상보다 훨씬 많은 source file에서 compile되기 때문입니다. 따라서 header file은 더 다양한 toolchain과 project configuration에 노출됩니다. Zephyr는 code style, compiler warning, static analyzer, MISRA 같은 standard compliance에 대해서도 엄격한 policy를 적용합니다.

이 제약을 모두 합치면 header file 작성은 매우 어려울 수 있습니다. 이 절은 Zephyr 맥락에서 얻은 header best practice와 경험을 기록합니다. 많은 내용이 Zephyr에만 해당하는 것은 아니며 C/C++ standard, 교재와 다른 reference를 대신하지 않습니다.

Testing

Zephyr project는 coverage baseline을 제공하고 문제를 조기에 잡으며 policy를 강제해 조합 폭증을 일정 수준 통제하는 광범위한 test·CI infrastructure를 갖고 있습니다. tests/lib/cpp/cxx/는 특히 유용합니다. testcase.yaml configuration을 통해 twister가 -std=c++98, -std=c++11 같은 여러 -std parameter를 빠르게 반복할 수 있습니다.

사용하지 않는 macro는 compile되지 않는다는 점을 기억하십시오.

Designated initializer

Initialization macro는 boilerplate code를 줄여 주므로 header file에서 흔히 사용합니다. C99는 struct와 union type을 bare expression 목록 대신 designated member name으로 초기화하는 기능을 추가했습니다. 일부 GCC version은 C90 mode에서도 designated initializer를 지원합니다.

단순하게 사용하면 designated initializer는 오류 가능성이 낮고 읽기 쉬우며 유연합니다. 하지만 C99는 매우 넓고 느슨한 사용을 허용했습니다. 순서를 바꾸거나 중복할 수 있고 .a.x =처럼 nested 지정이 가능하며 여러 brace를 생략하거나 designated initializer와 bare expression을 섞을 수도 있습니다.

20년 뒤 C++20도 designated initializer를 추가했지만 C++ struct가 실제로는 class이기도 하므로 훨씬 엄격합니다. C와 비교하는 C++ proposal P0329와 완전한 C++ reference가 설명하듯, designated와 non-designated initializer를 섞을 수 없고 initializer는 선언 순서대로 나와야 합니다. 중간 member를 건너뛰는 것은 허용됩니다.

새 C++20 restriction 때문에 같은 GCC version에서도 gcc -std=c++17로 compile되는 code가 gcc -std=c++20에서는 실패할 수 있습니다. C++17 이하에서는 C 방식으로 designated initializer와 bare expression을 섞는 것을 허용하지만 C++20에서는 compile되지 않습니다.

권고: 여러 C·C++ toolchain과 standard 사이 compatibility를 최대화하려면 Zephyr header file의 designated initializer가 C++20의 모든 rule과 restriction을 따라야 합니다. Designation을 쓰지 않는 pre-C99 initialization도 더 넓은 compatibility를 제공하고 허용되지만, 더 읽기 쉬운 designated initialization을 선호합니다. 어떤 경우에도 같은 initializer에서 두 style을 섞으면 안 됩니다.

경고: compile 성공이 비호환성 문제의 끝은 아닙니다. 예를 들어 C99에서는 initializer expression evaluation order가 지정되어 있지 않지만 C++20에서는 예상대로 왼쪽에서 오른쪽입니다. 다른 standard revision은 또 다를 수 있으므로 확실하지 않다면 여기뿐 아니라 다른 곳에서도 evaluation order에 의존하지 마십시오.

Anonymous union

Anonymous 또는 unnamed union은 C++ 초기부터 존재한 것으로 보이지만 C에는 C11에서 공식 추가되었습니다. 두 언어 사이에는 차이가 있습니다. 예를 들어 C는 enclosing struct 또는 union의 member로만 anonymous union을 지원합니다. 빈 목록 { }은 C++에서 항상 허용되었지만 C에서는 C23이 필요합니다.

Anonymous member를 초기화할 때 expression을 brace로 감쌀 수도 있고 감싸지 않을 수도 있으며 designated 또는 bare expression일 수 있습니다. Portability를 최대화하려면 anonymous union을 초기화할 때 다음을 따릅니다.

  • Designated initializer는 brace로 감싸지 마십시오. C++20 이상은 그런 brace를 bare expression과 다른 designated initializer를 섞는 것으로 해석해 compile에 실패합니다.
  • Bare expression은 brace로 감싸십시오. C에서는 이것이 필수입니다. C는 많은 initialization 형태와 변형을 허용해 모호성을 해소하려면 brace가 필요한 것으로 보입니다. 일반적으로 C는 initializer expression의 brace 대부분을 생략할 수 있지만 anonymous union을 bare expression으로 초기화하는 이 경우에는 생략할 수 없습니다.

일부 pre-C11 GCC version은 anonymous union의 일부 형태를 지원하지만 designated initializer를 brace로 감싸야 하므로 위 권고와 충돌합니다. #ifdef __STDC_VERSION__을 사용하면 해결할 수 있으며 Zephyr commit c15f029a7108과 해당 code review가 그 예를 보여 줍니다.

Source

출처

영어 원문에는 문단, 문서 지시문, 표, 코드, 코드 심벌, 경로를 그대로 보존했습니다.