yohhoyの日記

技術的メモをしていきたい日記

memccpy関数 in 標準C

C23標準ライブラリで追加されたmemccpy関数*1についてメモ。POSIX関数からの標準C入り。

コピー元s2からコピー先s1に対して、文字cの初回出現まで、もしくは最大n文字までコピーする関数。標準ヘッダ<string.h>にて宣言される。

void *memccpy(void * restrict s1, const void * restrict s2, int c, size_t n);

第3引数cにNUL文字('\0')を指定することで、有限長バッファへの安全な文字列コピーを実現できる。下記はOpenBSD strlcpy関数相当の実装例。*2

#include <string.h>

void my_strlcpy(char* dst, const char* src, size_t size)
{
  char *p = memccpy(dst, src, '\0', size);
  if ( !p ) {
    // コピー元範囲 [src, src+size) にNUL文字が存在しない場合、
    // コピー先末尾 dst[size-1] へのNUL代入で文字列を終端させる。
    dst[size-1] = '\0';
  }
}

const char *str = "Hello C";

char buf1[10];
my_strlcpy(buf1, str, sizeof(buf1);
// buf1 = "Hello C"

char buf2[5];
my_strlcpy(buf2, str, sizeof(buf2);
// buf2 = "Hell"

上記の文字列コピー処理は、古くからC標準ライブラリに存在するstrncpy関数(→id:yohhoy:20161102)よりも効率的に動作する。

void wasteful_strlcpy(char* dst, const char* src, size_t size)
{
  strncpy(dst, src, size);  // OK...だが非効率
  // strlen(src) < size-1 の場合、コピー先の残り領域にもNUL文字が設定される。
  // バッファサイズsizeが大きいほど無駄なメモリ書込処理が行われる。
  // またコピー先バッファ末尾へのNUL代入も必要となる。
  // strncpy関数の仕様上、戻り値からは代入の必要性を判定できない。
  dst[size-1] = '\0';
  // メモリ書込アクセスを嫌って分岐させるなら下記コードとなるが、
  // strncpy内部にてバッファ全域への書込アクセスが行われている...
  // if (dst[size-1] != '\0') { dst[size-1] = '\0'; }
}

C23 7.26.2.2/p2-3より引用。C++2c(C++26)はC23規格をNormative参照する。

Description
2 The memccpy function copies characters from the object pointed to by s2 into the object pointed to by s1, stopping after the first occurrence of character c (converted to an unsigned char) is copied, or after n characters are copied, whichever comes first. If copying takes place between objects that overlap, the behavior is undefined.
Returns
3 The memccpy function returns a pointer to the character after the copy of c in s1, or a null pointer if c was not found in the first n characters of s2.

関連URL

*1:ANSI C時代から存在する memcpy 関数とは別モノ。関数名 memccpy にcが2個含まれることに注意!

*2:OpenBSDのstrlcpy関数は実際にコピーされた文字列長を返す。

ゼロ幅ビットフィールド

プログラミング言語C/C++では、ビット幅が値0の無名(unnamed)ビットフィールドを宣言できる。ゼロ幅ビットフィールドにより、その前後ビットフィールドへの割当バイト(byte)を分離する。

struct S {
  int b1 : 4;
  int b2 : 2;
  int : 0;  // (無名)ゼロ幅ビットフィールド
  int b3 : 2;
};

上記例のビットフィールドb1, b2は同一バイト位置に割り当てられるが、ゼロ幅ビットフィールドを挟んだb3は前2つとは異なるバイト位置に割り当てられることが保証される。

C言語

C99 6.7.2.1/p3, 11より一部引用(下線部は強調)。

Syntax
(snip)
struct-declarator:
  declarator
  declaratoropt : constant-expression

3 The expression that specifies the width of a bit-field shall be an integer constant expression with a nonnegative value that does not exceed the width of an object of the type that would be specified were the colon and expression omitted. If the value is zero, the declaration shall have no declarator.

11 A bit-field declaration with no declarator, but only a colon and a width, indicates an unnamed bit-field. As a special case, a bit-field structure member with a width of 0 indicates that no further bit-field is to be packed into the unit in which the previous bit-field, if any, was placed.

C++言語

C++03 9.6/p1-2より引用(下線部は強調)。

1 A member-declarator of the form
  identifieropt : constant-expression
specifies a bit-field; its length is set off from the bit-field name by a colon. The bit-field attribute is not part of the type of the class member. The constant-expression shall be an integral constant-expression with a value greater than or equal to zero. The constant-expression may be larger than the number of bits in the object representation (3.9) of the bit-field’s type; in such cases the extra bits are used as padding bits and do not participate in the value representation (3.9) of the bit-field. Allocation of bit-fields within a class object is implementation-defined. Alignment of bit-fields is implementation-defined. Bit-fields are packed into some addressable allocation unit. [Note: bit-fields straddle allocation units on some machines and not on others. Bit-fields are assigned right-to-left on some machines, left-to-right on others. ]
2 A declaration for a bit-field that omits the identifier declares an unnamed bit-field. Unnamed bit-fields are not members and cannot be initialized. [Note: an unnamed bit-field is useful for padding to conform to externally-imposed layouts. ] As a special case, an unnamed bit-field with a width of zero specifies alignment of the next bit-field at an allocation unit boundary. Only when declaring an unnamed bit-field may the constant-expression be a value equal to zero.

関連URL

Case Ranges構文 in 標準C

プログラミング言語Cの次期標準C2yでは、swich構文のcaseラベルにおける数値範囲指定をサポートする。GNU C拡張の標準化。

// C2y
switch (n) {
case 1 ... 3:
  // 値1,2,3の処理
  break;
case 4 ... 6:
  // 値4,5,6の処理
  break;
}

2個の定数式(constant-expression)と...から構成され、caseラベルでのみ利用可能な定数範囲式(constant-range-expression)が追加される。構文規則上は...前後の空白文字を直接は要求しないが、浮動小数点数リテラルへの誤解釈を防ぐため常に空白を開けることが推奨される。

// C2y
enum { L = 0, H = 1 };
// または
constexpr int L = 0;
constexpr int H = 1;

switch (n) {
case L...H:  // OK: 曖昧さなく解釈可能
  // 値0,1の処理
  break;
}

また、C++2c向けに同機能の導入を目指す提案文書P4040が検討されている。*1

関連URL

OBJC_BOOL_IS_BOOLマクロ

Objective-Cランタイムで定義される風変わりな名前のマクロ。

Macro
OBJC_BOOL_IS_BOOL
iOS | iPadOS | Mac Catalyst | macOS | tvOS | visionOS | watchOS

#define OBJC_BOOL_IS_BOOL
OBJC_BOOL_IS_BOOL | Apple Developer Documentation

Objective-C BOOL型==C言語 bool型となる環境において定義される。BOOL==signed char型となる環境では、別マクロOBJC_BOOL_IS_CHARが定義される。
https://github.com/opensource-apple/objc4/blob/master/runtime/objc.h より引用。

/// Type to represent a boolean value.
#if (TARGET_OS_IPHONE && __LP64__)  ||  TARGET_OS_WATCH
#define OBJC_BOOL_IS_BOOL 1
typedef bool BOOL;
#else
#define OBJC_BOOL_IS_CHAR 1
typedef signed char BOOL; 
// BOOL is explicitly signed so @encode(BOOL) == "c" rather than "C" 
// even if -funsigned-char is used.
#endif

関連URL

C/C++ char型のビット幅定義

C23/C++23現在のプログラミング言語仕様では、char型のビット幅CHAR_BITは8bit以上の処理系定義(implementation-defined)と規定される。POSIX規格では厳密にCHAR_BIT==8と定義する。

POSIX

IEEE Std 1003.1, 2004, limits.hヘッダ仕様より一部引用。

Numerical Limits
{CHAR_BIT}
Number of bits in a type char.
 Value: 8

CHANGE HISTORY
Issue 6
The values for the limits {CHAR_BIT}, {SCHAR_MAX}, and {UCHAR_MAX} are now required to be 8, +127, and 255, respectively.

C++

C++標準ライブラリ仕様定義はC標準規格を規範的(normative)に参照している。C++23 17.3.6より一部引用。*1

#define CHAR_BIT see below

The header <climits> defines all macros the same as the C standard library header <limits.h>.
(snip)
See also: ISO C 5.2.4.2.1

C++2c(C++26)向けにCHAR_BIT==8を規定する提案文書P3477R5が検討されていたが、複数の強い反対意見(PDF)P3633R0, (PDF)P3635R0も提出され、2025年2月会合の投票では合意に至らず(not consensus)同提案は破棄された。*2

C

C23 5.2.5.3.2より一部引用(下線部は強調)。C17 5.2.4.2.1も実質的に同一内容。

The values given subsequently shall be replaced by constant expressions suitable for use in conditional expression inclusion preprocessing directives. Their implementation-defined values shall be equal or greater to those shown.

  • number of bits for smallest object that is not a bit-field (byte)
CHAR_BIT  8

(snip)

関連URL

*1:C++23標準規格はC17(ISO/IEC 9899:2018)を引用規格(normative reference)と定める。

*2:https://github.com/cplusplus/papers/issues/2131

-fassume-nothrow-exception-dtorオプション

LLVM/Clangでは「C++例外オブジェクトのデストラクタで例外送出しないと仮定する」コンパイルオプション-fassume-nothrow-exception-dtorが提供される。こんな邪悪な例外クラスを扱うユースケースは無いだろうが、C++言語仕様上は禁止されていない。

// デストラクタで例外送出する例外オブジェクト
struct evil_exception {
  ~evil_exception() noexcept(false) { throw 42; }
};

void func()
{
  try { throw evil_exception{}; }
  catch (...) { std::puts("1st catch"); }
}

int main()
{
  try { func(); }
  catch (...) { std::puts("2nd catch"); }
}

Clangに-fassume-nothrow-exception-dtorオプション指定すると、上記evil_exceptionデストラクタはコンパイルエラーとして拒絶する。

error: cannot throw object of type 'evil_exception' with a potentially-throwing destructor

Clang 18で導入されたコンパイルオプション。

New Compiler Flags
-fassume-nothrow-exception-dtor is added to assume that the destructor of a thrown exception object will not throw. The generated code for catch handlers will be smaller. A throw expression of a type with a potentially-throwing destructor will lead to an error.

Clang 18.1.1 Release Notes — Clang 18.1.1 documentation

-fassume-nothrow-exception-dtor
Assume that an exception object' destructor will not throw, and generate less code for catch handlers. A throw expression of a type with a potentially-throwing destructor will lead to an error.

By default, Clang assumes that the exception object may have a throwing destructor. For the Itanium C++ ABI, Clang generates a landing pad to destroy local variables and call _Unwind_Resume for the code catch (...) { ... }. This option tells Clang that an exception object’s destructor will not throw and code simplification is possible.

Clang Compiler User’s Manual — Clang 18.1.1 documentation

おまけ

C++処理系毎に実行結果が異なる模様。いずれにせよ実用性は皆無。

GCC, Clang/libstdc++の実行結果:

1st catch
2nd catch

Clang/libc++の実行結果:

libc++abi: terminating due to uncaught exception of type int
Program terminated with signal: SIGSEGV

MSVC v19.50の実行結果:std::terminate呼び出し結果に相当する例外コード0xc0000409 STATUS_STACK_BUFFER_OVERRUNでプロセス異常終了する。*1

関連URL:

冗長なtypenameキーワード

プログラミング言語C++において型(type)名を記述するとき、C++11以降では修飾名(qualified name)に限って冗長なtypenameキーワードを記述しても良い。

#include <cstddef>
using std::size_t;
struct S { using type = int; };

typename int x0;     // NG
typename size_t n0;  // NG

typename std::size_t n1;  // OK
typename ::size_t n2;     // OK
typename S::type v1;      // OK

C++03時点ではtypenameキーワードの明記はテンプレート定義内に限定されていたが、Core Working Group Issue#382により修正された経緯がある。同Issueより一部引用(下線部は強調)。

P. J. Plauger, among others, has noted that typename is hard to use, because in a given context it's either required or forbidden, and it's often hard to tell which. It would make life easier for programmers if typename could be allowed in places where it is not required, e.g., outside of templates

Notes from the April 2003 meeting:
There was unanimity on relaxing this requirement on typename. The question was how much to relax it. Everyone agreed on allowing it on all qualified names, which is an easy fix (no syntax change required). But should it be allowed other places? P. J. Plauger said he'd like to see it allowed anywhere a type name is allowed, and that it could actually be a decades-late assist for the infamous "the ice is thin here" typedef problem noted in K&R I.

Notes from October 2003 meeting:
We considered whether typename should be allowed in more places, and decided we only wanted to allow it in qualified names (for now at least).

おまけ:CWG 382で言及される “typedef problem” に関して、K&R C 1st Ed., §11.1 Lexical scopeより言及箇所を引用する。C言語では「変数名のない空の宣言*1」が許容されることと、「型名省略時の暗黙int宣言」はC99で削除されたため、現在からみると意図を読み取りづらい。

In all cases, however, if an identifier is explicitly declared at the head of a block, including the block constituting a function, any declaration of that identifier outside the block is suspended until the end of the block.

Remember also (§8.5) that identifiers associated with ordinary variables on the one hand and those associated with structure and union members and tags on the other form two disjoint classes which do not conflict. Members and tags follow the same scope rules as other identifiers. typedef names are in the same class as ordinary identifiers. They may be redeclared in inner blocks, but an explicit type must be given in the inner declaration:

typedef float distance;
...
{
    auto int distance;
    ...

The int must be present in the second declaration, or it would be taken to be a declaration with no declarators and type distance.

脚注† lt is agreed that the ice is thin here.

関連URL

*1:GCCでは警告"useless type name in empty declaration"、Clangでは警告"declaration does not declare anything"として検知される。