mirror of
https://gitlab.freedesktop.org/NetworkManager/NetworkManager
synced 2024-10-15 20:45:32 +00:00
Squashed 'src/c-stdaux/' changes from 99fe83cd5698..1407a1fb2754
1407a1fb2754 ci: add clang to RTD scripts 8404c4ca6d06 ci: build documentation 044c65c2e134 docs: add sphinx-based API documentation ad8449068d96 ci: use ci-c-util for macos runs 179987035687 build: adjust for v1.1.0 release 55d787178c84 build: prepare v1.1.0 release 29ca943e636a c-stdaux: avoid NULL arithmetic even in dead-code c6358e956c29 c-stdaux: avoid NULL-dereference in constant expressions 7fab258bdf6a c-stdaux: improve kerneldoc comments 434b75a796c0 build: export 'version-scripts' configuration 33d56a6aecb9 c-stdaux: encapsulate C_EXPR_ASSERT() aca7ee0ece60 ci: add macos run 0aa338b1f0de test: prefer pipe() over eventfd() for portability adda5ff3e9d9 build: export cflags via pkg-config 7a8493bebc59 api: add c_memcpy() a01615aefe48 build: prepare v1.0.0 1685fc39db3d api: provide c_memzero() 1257244f886a api: add c_memset() git-subtree-dir: src/c-stdaux git-subtree-split: 1407a1fb275494f9efc1abbef2fd19856fb1f43d
This commit is contained in:
parent
1c260f5a96
commit
9d1772bd73
17
.github/workflows/ci.yml
vendored
17
.github/workflows/ci.yml
vendored
|
@ -7,11 +7,24 @@ on:
|
||||||
- cron: '0 0 * * *'
|
- cron: '0 0 * * *'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
ci:
|
ci-linux:
|
||||||
name: CI with Default Configuration
|
name: Linux CI
|
||||||
uses: bus1/cabuild/.github/workflows/ci-c-util.yml@v1
|
uses: bus1/cabuild/.github/workflows/ci-c-util.yml@v1
|
||||||
with:
|
with:
|
||||||
cabuild_ref: "v1"
|
cabuild_ref: "v1"
|
||||||
|
linux: true
|
||||||
m32: true
|
m32: true
|
||||||
matrixmode: true
|
matrixmode: true
|
||||||
valgrind: true
|
valgrind: true
|
||||||
|
ci-macos:
|
||||||
|
name: MacOS CI
|
||||||
|
uses: bus1/cabuild/.github/workflows/ci-c-util.yml@v1
|
||||||
|
with:
|
||||||
|
cabuild_ref: "v1"
|
||||||
|
linux: false
|
||||||
|
macos: true
|
||||||
|
ci-docs:
|
||||||
|
name: Documentation CI
|
||||||
|
uses: bus1/cabuild/.github/workflows/ci-sphinx.yml@main
|
||||||
|
with:
|
||||||
|
source: "./src/docs"
|
||||||
|
|
20
.readthedocs.yaml
Normal file
20
.readthedocs.yaml
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# Read the Docs configuration file
|
||||||
|
|
||||||
|
version: 2
|
||||||
|
|
||||||
|
build:
|
||||||
|
apt_packages:
|
||||||
|
- "clang"
|
||||||
|
os: "ubuntu-22.04"
|
||||||
|
tools:
|
||||||
|
python: "3"
|
||||||
|
|
||||||
|
formats: "all"
|
||||||
|
|
||||||
|
python:
|
||||||
|
install:
|
||||||
|
- requirements: "src/docs/requirements.txt"
|
||||||
|
system_packages: true
|
||||||
|
|
||||||
|
sphinx:
|
||||||
|
configuration: "src/docs/conf.py"
|
4
AUTHORS
4
AUTHORS
|
@ -34,5 +34,9 @@ COPYRIGHT: (ordered alphabetically)
|
||||||
|
|
||||||
AUTHORS: (ordered alphabetically)
|
AUTHORS: (ordered alphabetically)
|
||||||
David Rheinsberg <david.rheinsberg@gmail.com>
|
David Rheinsberg <david.rheinsberg@gmail.com>
|
||||||
|
Evgeny Vereshchagin <evvers@ya.ru>
|
||||||
|
Lorenzo Arena <lorenzo.arena@powersoft.com>
|
||||||
|
Michele Dionisio <michele.dionisio@gmail.com>
|
||||||
Thomas Haller <thaller@redhat.com>
|
Thomas Haller <thaller@redhat.com>
|
||||||
Tom Gundersen <teg@jklm.no>
|
Tom Gundersen <teg@jklm.no>
|
||||||
|
Yuri Chornoivan <yurchor@ukr.net>
|
||||||
|
|
37
NEWS.md
37
NEWS.md
|
@ -1,11 +1,38 @@
|
||||||
# c-stdaux - Auxiliary macros and functions for the C standard library
|
# c-stdaux - Auxiliary macros and functions for the C standard library
|
||||||
|
|
||||||
## CHANGES WITH 1:
|
## CHANGES WITH 1.1.0:
|
||||||
|
|
||||||
|
* Add c_memcpy() as a safe wrapper around memcpy(3) that supports
|
||||||
|
empty arenas as NULL pointers.
|
||||||
|
|
||||||
|
* Support building on MacOS-X.
|
||||||
|
|
||||||
|
* Rework the apidoc comments and properly document the entire API.
|
||||||
|
|
||||||
|
* Export 'version-scripts' configuration variable alongside the
|
||||||
|
existing 'cflags' variable. It defines whether c-stdaux was built
|
||||||
|
with GNU-linker version-scripts, or not. Dependent projects can
|
||||||
|
use this to decide whether to use version-scripts or not.
|
||||||
|
Additionally, the new 'version-scripts' meson-option allows
|
||||||
|
specifying whether to use version-scripts, auto-detect whether to
|
||||||
|
enable it, or disable it.
|
||||||
|
|
||||||
|
* Fix the export of `cflags` to also be exported in pkg-config, not
|
||||||
|
just meson subprojects.
|
||||||
|
|
||||||
|
* Avoid NULL-pointers in compile-time macros. This silences possible
|
||||||
|
false-positives from code sanitizers that otherwise trip over the
|
||||||
|
NULL pointer dereferences.
|
||||||
|
|
||||||
|
Contributions from: David Rheinsberg, Evgeny Vereshchagin
|
||||||
|
|
||||||
|
- Brno, 2022-06-22
|
||||||
|
|
||||||
|
## CHANGES WITH 1.0.0:
|
||||||
|
|
||||||
* Initial release of c-stdaux.
|
* Initial release of c-stdaux.
|
||||||
|
|
||||||
* TBD
|
Contributions from: David Rheinsberg, Lorenzo Arena, Michele Dionisio,
|
||||||
|
Yuri Chornoivan
|
||||||
|
|
||||||
Contributions from: TBD
|
- Dußlingen, 2022-05-12
|
||||||
|
|
||||||
- TBD, YYYY-MM-DD
|
|
||||||
|
|
26
meson.build
26
meson.build
|
@ -1,3 +1,7 @@
|
||||||
|
#
|
||||||
|
# Global Project Setup
|
||||||
|
#
|
||||||
|
|
||||||
project(
|
project(
|
||||||
'c-stdaux',
|
'c-stdaux',
|
||||||
'c',
|
'c',
|
||||||
|
@ -6,7 +10,7 @@ project(
|
||||||
],
|
],
|
||||||
license: 'Apache',
|
license: 'Apache',
|
||||||
meson_version: '>=0.60.0',
|
meson_version: '>=0.60.0',
|
||||||
version: '1.0.0',
|
version: '1.1.0',
|
||||||
)
|
)
|
||||||
major = meson.project_version().split('.')[0]
|
major = meson.project_version().split('.')[0]
|
||||||
project_description = 'Auxiliary macros and functions for the C standard library'
|
project_description = 'Auxiliary macros and functions for the C standard library'
|
||||||
|
@ -23,6 +27,7 @@ mod_pkgconfig = import('pkgconfig')
|
||||||
# well. Since these exports are limited to strings, we need to be careful that
|
# well. Since these exports are limited to strings, we need to be careful that
|
||||||
# the individual entries do not contain spaces (see the assertion below).
|
# the individual entries do not contain spaces (see the assertion below).
|
||||||
#
|
#
|
||||||
|
|
||||||
cflags = meson.get_compiler('c').get_supported_arguments(
|
cflags = meson.get_compiler('c').get_supported_arguments(
|
||||||
# Enable GNU features of our dependencies. See feature_test_macros(7).
|
# Enable GNU features of our dependencies. See feature_test_macros(7).
|
||||||
'-D_GNU_SOURCE',
|
'-D_GNU_SOURCE',
|
||||||
|
@ -81,6 +86,25 @@ cflags = meson.get_compiler('c').get_supported_arguments(
|
||||||
assert(not ''.join(cflags).contains(' '), 'Malformed compiler flags.')
|
assert(not ''.join(cflags).contains(' '), 'Malformed compiler flags.')
|
||||||
add_project_arguments(cflags, language: 'c')
|
add_project_arguments(cflags, language: 'c')
|
||||||
|
|
||||||
|
#
|
||||||
|
# Version Scripts
|
||||||
|
#
|
||||||
|
|
||||||
|
use_version_scripts = get_option('version-scripts')
|
||||||
|
if use_version_scripts == 'auto'
|
||||||
|
use_version_scripts = meson.get_compiler('c').has_link_argument(
|
||||||
|
'-Wl,--version-script=' + (meson.current_source_dir() / 'src/libcstdaux.sym')
|
||||||
|
) ? 'yes' : 'no'
|
||||||
|
endif
|
||||||
|
|
||||||
|
#
|
||||||
|
# Subdir Delegation
|
||||||
|
#
|
||||||
|
|
||||||
subdir('src')
|
subdir('src')
|
||||||
|
|
||||||
|
#
|
||||||
|
# Meson Subproject Configuration
|
||||||
|
#
|
||||||
|
|
||||||
meson.override_dependency('libcstdaux-'+major, libcstdaux_dep, static: true)
|
meson.override_dependency('libcstdaux-'+major, libcstdaux_dep, static: true)
|
||||||
|
|
7
meson_options.txt
Normal file
7
meson_options.txt
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
option(
|
||||||
|
'version-scripts',
|
||||||
|
choices: ['yes', 'no', 'auto'],
|
||||||
|
description: 'Enable GNU-version-scripts for linking',
|
||||||
|
type: 'combo',
|
||||||
|
value: 'auto',
|
||||||
|
)
|
587
src/c-stdaux.h
587
src/c-stdaux.h
|
@ -1,12 +1,23 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Auxiliary macros and functions for the C standard library
|
* c-stdaux: Auxiliary macros and functions for the C standard library
|
||||||
*
|
*
|
||||||
* The `c-stdaux.h` header contains a collection of auxiliary macros and helper
|
* Main public header of the c-stdaux library. All includes of this header are
|
||||||
* functions around the functionality provided by the different C standard
|
* part of the API!
|
||||||
* library implementations, as well as other specifications implemented by
|
*/
|
||||||
* them.
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DOC:
|
||||||
|
*
|
||||||
|
* The ``c-stdaux.h`` header contains a collection of auxiliary macros and
|
||||||
|
* helper functions around the functionality provided by the different C
|
||||||
|
* standard library implementations, as well as other specifications
|
||||||
|
* implemented by them.
|
||||||
*
|
*
|
||||||
* Most of the helpers provided here provide aliases for common library and
|
* Most of the helpers provided here provide aliases for common library and
|
||||||
* compiler features. Furthermore, several helpers simply provide other calling
|
* compiler features. Furthermore, several helpers simply provide other calling
|
||||||
|
@ -16,17 +27,27 @@
|
||||||
*
|
*
|
||||||
* The namespace used by this project is:
|
* The namespace used by this project is:
|
||||||
*
|
*
|
||||||
* * `c_*` for all common C symbols or definitions that behave like proper C
|
* - ``c_*`` for all common C symbols or definitions that behave like proper C
|
||||||
* entities (e.g., macros that protect against double-evaluation would use
|
* entities (e.g., macros that protect against double-evaluation would use
|
||||||
* lower-case names)
|
* lower-case names).
|
||||||
*
|
*
|
||||||
* * `C_*` for all constants, as well as macros that may not be safe against
|
* - ``C_*`` for all constants, as well as macros that may not be safe against
|
||||||
* double evaluation.
|
* double evaluation.
|
||||||
|
*
|
||||||
|
* - ``c_internal_*`` and ``C_INTERNAL_*`` for all internal symbols that
|
||||||
|
* should not be invoked by the caller and are not part of the API
|
||||||
|
* guarantees.
|
||||||
*/
|
*/
|
||||||
|
/**/
|
||||||
|
|
||||||
#ifdef __cplusplus
|
/**
|
||||||
extern "C" {
|
* DOC: Guaranteed Includes
|
||||||
#endif
|
*
|
||||||
|
* The ``c-stdaux.h`` header includes a set of C Standard Library headers as
|
||||||
|
* well as UNIX headers. All those includes are guaranteed and part of the API.
|
||||||
|
* See the actual header for a comprehensive list.
|
||||||
|
*/
|
||||||
|
/**/
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <dirent.h>
|
#include <dirent.h>
|
||||||
|
@ -48,44 +69,152 @@ extern "C" {
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Shortcuts for gcc attributes. See GCC manual for details. They're 1-to-1
|
* DOC: Compiler Attributes
|
||||||
* mappings to the GCC equivalents. No additional magic here. They are
|
*
|
||||||
* supported by other compilers as well.
|
* The GCC compiler uses the ``__attribute__((__xyz__()))`` syntax to annotate
|
||||||
|
* language entities with special attributes. Aliases are provided by this
|
||||||
|
* header which map one-to-one to the respective compiler attributes.
|
||||||
|
*
|
||||||
|
* These attributes are not supported by all compilers, but are always provided
|
||||||
|
* by this header. They are pre-processor macros and do not affect the
|
||||||
|
* compilation, unless used. Note that most compilers support these, not just
|
||||||
|
* GCC.
|
||||||
|
*/
|
||||||
|
/**/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* _c_cleanup_() - Cleanup attribute
|
||||||
|
* @_x: Cleanup function to use
|
||||||
|
*
|
||||||
|
* Alias for ``__attribute__((__cleanup__(_x)))``.
|
||||||
*/
|
*/
|
||||||
#define _c_cleanup_(_x) __attribute__((__cleanup__(_x)))
|
#define _c_cleanup_(_x) __attribute__((__cleanup__(_x)))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* _c_const_() - Const attribute
|
||||||
|
*
|
||||||
|
* Alias for ``__attribute__((__const__))``.
|
||||||
|
*/
|
||||||
#define _c_const_ __attribute__((__const__))
|
#define _c_const_ __attribute__((__const__))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* _c_deprecated_() - Deprecated attribute
|
||||||
|
*
|
||||||
|
* Alias for ``__attribute__((__deprecated__))``.
|
||||||
|
*/
|
||||||
#define _c_deprecated_ __attribute__((__deprecated__))
|
#define _c_deprecated_ __attribute__((__deprecated__))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* _c_hidden_() - Hidden attribute
|
||||||
|
*
|
||||||
|
* Alias for ``__attribute__((__visibility__("hidden")))``.
|
||||||
|
*/
|
||||||
#define _c_hidden_ __attribute__((__visibility__("hidden")))
|
#define _c_hidden_ __attribute__((__visibility__("hidden")))
|
||||||
#define _c_likely_(_x) (__builtin_expect(!!(_x), 1))
|
|
||||||
|
/**
|
||||||
|
* _c_packed_() - Packed attribute
|
||||||
|
*
|
||||||
|
* Alias for ``__attribute__((__packed__))``.
|
||||||
|
*/
|
||||||
#define _c_packed_ __attribute__((__packed__))
|
#define _c_packed_ __attribute__((__packed__))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* _c_printf_() - Printf attribute
|
||||||
|
* @_a: Format expression argument index
|
||||||
|
* @_b: First format-parameter argument index
|
||||||
|
*
|
||||||
|
* Alias for ``__attribute__((__format__(printf, _a, _b)))``.
|
||||||
|
*/
|
||||||
#define _c_printf_(_a, _b) __attribute__((__format__(printf, _a, _b)))
|
#define _c_printf_(_a, _b) __attribute__((__format__(printf, _a, _b)))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* _c_public_() - Public attribute
|
||||||
|
*
|
||||||
|
* Alias for ``__attribute__((__visibility__("default")))``.
|
||||||
|
*/
|
||||||
#define _c_public_ __attribute__((__visibility__("default")))
|
#define _c_public_ __attribute__((__visibility__("default")))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* _c_pure_() - Pure attribute
|
||||||
|
*
|
||||||
|
* Alias for ``__attribute__((__pure__))``.
|
||||||
|
*/
|
||||||
#define _c_pure_ __attribute__((__pure__))
|
#define _c_pure_ __attribute__((__pure__))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* _c_sentinel_() - Sentinel attribute
|
||||||
|
*
|
||||||
|
* Alias for ``__attribute__((__sentinel__))``.
|
||||||
|
*/
|
||||||
#define _c_sentinel_ __attribute__((__sentinel__))
|
#define _c_sentinel_ __attribute__((__sentinel__))
|
||||||
#define _c_unlikely_(_x) (__builtin_expect(!!(_x), 0))
|
|
||||||
|
/**
|
||||||
|
* _c_unused_() - Unused attribute
|
||||||
|
*
|
||||||
|
* Alias for ``__attribute__((__unused__))``.
|
||||||
|
*/
|
||||||
#define _c_unused_ __attribute__((__unused__))
|
#define _c_unused_ __attribute__((__unused__))
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* C_EXPR_ASSERT() - create expression with assertion
|
* DOC: Compiler Intrinsics
|
||||||
* @_expr: expression to evaluate to
|
|
||||||
* @_assertion: arbitrary assertion
|
|
||||||
* @_message: message associated with the assertion
|
|
||||||
*
|
*
|
||||||
* This macro simply evaluates to @_expr. That is, it can be used in any
|
* Aliases for common compiler extensions and intrinsics are provided similar
|
||||||
* context that expects an expression like @_expr. Additionally, it takes an
|
* to the compiler attributes. They are pure preprocessor aliases and do not
|
||||||
* assertion as @_assertion and evaluates it through _Static_assert(), using
|
* affect compilation unless used.
|
||||||
* @_message as debug message.
|
|
||||||
*
|
|
||||||
* The _Static_assert() builtin of C11 is defined as statement and thus cannot
|
|
||||||
* be used in expressions. This macro circumvents this restriction.
|
|
||||||
*
|
|
||||||
* Return: Evaluates to @_expr.
|
|
||||||
*/
|
*/
|
||||||
|
/**/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* _c_likely_() - Likely attribute
|
||||||
|
* @_x: Expression to evaluate
|
||||||
|
*
|
||||||
|
* Alias for ``__builtin_expect(!!(_x), 1)``.
|
||||||
|
*
|
||||||
|
* Return: The expression ``_x`` is evaluated and returned.
|
||||||
|
*/
|
||||||
|
#define _c_likely_(_x) (__builtin_expect(!!(_x), 1))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* _c_unlikely_() - Unlikely attribute
|
||||||
|
* @_x: Expression to evaluate
|
||||||
|
*
|
||||||
|
* Alias for ``__builtin_expect(!!(_x), 0)``.
|
||||||
|
*
|
||||||
|
* Return: The expression ``_x`` is evaluated and returned.
|
||||||
|
*/
|
||||||
|
#define _c_unlikely_(_x) (__builtin_expect(!!(_x), 0))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DOC: Utility Macros
|
||||||
|
*
|
||||||
|
* A set of utility macros is provided which aids in creating safe macros
|
||||||
|
* suitable for use in other pre-processor statements as well as in C
|
||||||
|
* expressions.
|
||||||
|
*/
|
||||||
|
/**/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* C_EXPR_ASSERT() - Create expression with assertion
|
||||||
|
* @_expr: Expression to evaluate to
|
||||||
|
* @_assertion: Arbitrary assertion
|
||||||
|
* @_message: Message associated with the assertion
|
||||||
|
*
|
||||||
|
* This macro simply evaluates to ``_expr``. That is, it can be used in any
|
||||||
|
* context that expects an expression like ``_expr``. Additionally, it takes an
|
||||||
|
* assertion as ``_assertion`` and evaluates it through ``_Static_assert()``,
|
||||||
|
* using ``_message`` as debug message.
|
||||||
|
*
|
||||||
|
* The ``_Static_assert()`` builtin of C11 is defined as statement and thus
|
||||||
|
* cannot be used in expressions. This macro circumvents this restriction.
|
||||||
|
*
|
||||||
|
* Return: Evaluates to ``_expr``.
|
||||||
|
*/
|
||||||
|
#define C_EXPR_ASSERT(_expr, _assertion, _message) C_INTERNAL_EXPR_ASSERT((_expr), (_assertion), _message)
|
||||||
#if defined(__COVERITY__) // Coverity cannot const-fold __builtin_choose_expr()
|
#if defined(__COVERITY__) // Coverity cannot const-fold __builtin_choose_expr()
|
||||||
# define C_EXPR_ASSERT(_expr, _assertion, _message) (_expr)
|
# define C_INTERNAL_EXPR_ASSERT(_expr, _assertion, _message) (_expr)
|
||||||
#else
|
#else
|
||||||
# define C_EXPR_ASSERT(_expr, _assertion, _message) \
|
# define C_INTERNAL_EXPR_ASSERT(_expr, _assertion, _message) \
|
||||||
/* indentation and line-split to get better diagnostics */ \
|
/* indentation and line-split to get better diagnostics */ \
|
||||||
(__builtin_choose_expr( \
|
(__builtin_choose_expr( \
|
||||||
!!(1 + 0 * sizeof( \
|
!!(1 + 0 * sizeof( \
|
||||||
|
@ -99,8 +228,8 @@ _Static_assert(_assertion, _message); \
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* C_STRINGIFY() - stringify a token, but evaluate it first
|
* C_STRINGIFY() - Stringify a token, but evaluate it first
|
||||||
* @_x: token to evaluate and stringify
|
* @_x: Token to evaluate and stringify
|
||||||
*
|
*
|
||||||
* Return: Evaluates to a constant string literal
|
* Return: Evaluates to a constant string literal
|
||||||
*/
|
*/
|
||||||
|
@ -108,9 +237,9 @@ _Static_assert(_assertion, _message); \
|
||||||
#define C_INTERNAL_STRINGIFY(_x) #_x
|
#define C_INTERNAL_STRINGIFY(_x) #_x
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* C_CONCATENATE() - concatenate two tokens, but evaluate them first
|
* C_CONCATENATE() - Concatenate two tokens, but evaluate them first
|
||||||
* @_x: first token
|
* @_x: First token
|
||||||
* @_y: second token
|
* @_y: Second token
|
||||||
*
|
*
|
||||||
* Return: Evaluates to a constant identifier
|
* Return: Evaluates to a constant identifier
|
||||||
*/
|
*/
|
||||||
|
@ -118,8 +247,8 @@ _Static_assert(_assertion, _message); \
|
||||||
#define C_INTERNAL_CONCATENATE(_x, _y) _x ## _y
|
#define C_INTERNAL_CONCATENATE(_x, _y) _x ## _y
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* C_EXPAND() - expand a tuple to a series of its values
|
* C_EXPAND() - Expand a tuple to a series of its values
|
||||||
* @_x: tuple to expand
|
* @_x: Tuple to expand
|
||||||
*
|
*
|
||||||
* Return: Evaluates to the expanded tuple
|
* Return: Evaluates to the expanded tuple
|
||||||
*/
|
*/
|
||||||
|
@ -127,29 +256,32 @@ _Static_assert(_assertion, _message); \
|
||||||
#define C_INTERNAL_EXPAND(...) __VA_ARGS__
|
#define C_INTERNAL_EXPAND(...) __VA_ARGS__
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* C_VAR() - generate unique variable name
|
* C_VAR() - Generate unique variable name
|
||||||
* @_x: name of variable, optional
|
* @_x: Name of variable, optional
|
||||||
* @_uniq: unique prefix, usually provided by __COUNTER__, optional
|
* @_uniq: Unique prefix, usually provided by ``__COUNTER__``, optional
|
||||||
*
|
*
|
||||||
* This macro shall be used to generate unique variable names, that will not be
|
* This macro shall be used to generate unique variable names, that will not be
|
||||||
* shadowed by recursive macro invocations. It is effectively a
|
* shadowed by recursive macro invocations. It is effectively a
|
||||||
* C_CONCATENATE of both arguments, but also provides a globally separated
|
* :c:macro:`C_CONCATENATE` of both arguments, but also provides a globally
|
||||||
* prefix and makes the code better readable.
|
* separated prefix and makes the code better readable.
|
||||||
*
|
*
|
||||||
* The second argument is optional. If not given, __LINE__ is implied, and as
|
* The second argument is optional. If not given, ``__LINE__`` is implied, and
|
||||||
* such the macro will generate the same identifier if used multiple times on
|
* as such the macro will generate the same identifier if used multiple times
|
||||||
* the same code-line (or within a macro). This should be used if recursive
|
* on the same code-line (or within a macro). This should be used if recursive
|
||||||
* calls into the macro are not expected. In fact, no argument is necessary in
|
* calls into the macro are not expected. In fact, no argument is necessary in
|
||||||
* this case, as a mere `C_VAR` will evaluate to a valid variable name.
|
* this case, as a mere ``C_VAR`` will evaluate to a valid variable name.
|
||||||
*
|
*
|
||||||
* This helper may be used by macro implementations that might reasonable well
|
* This helper may be used by macro implementations that might reasonable well
|
||||||
* be called in a stacked fasion, like:
|
* be called in a stacked fasion, like:
|
||||||
*
|
*
|
||||||
|
* .. code-block:: c
|
||||||
|
*
|
||||||
* c_max(foo, c_max(bar, baz))
|
* c_max(foo, c_max(bar, baz))
|
||||||
*
|
*
|
||||||
* Such a stacked call of c_max() might cause compiler warnings of shadowed
|
* Such a stacked call of :c:macro:`c_max()` might cause compiler warnings of
|
||||||
* variables in the definition of c_max(). By using C_VAR(), such warnings
|
* shadowed variables in the definition of :c:macro:`c_max()`. By using
|
||||||
* can be silenced as each evaluation of c_max() uses unique variable names.
|
* ``C_VAR()``, such warnings can be silenced as each evaluation of
|
||||||
|
* :c:macro:`c_max()` uses unique variable names.
|
||||||
*
|
*
|
||||||
* Return: This evaluates to a constant identifier.
|
* Return: This evaluates to a constant identifier.
|
||||||
*/
|
*/
|
||||||
|
@ -159,28 +291,28 @@ _Static_assert(_assertion, _message); \
|
||||||
#define C_VAR2(_x, _uniq) C_CONCATENATE(c_internal_var_unique_, C_CONCATENATE(_uniq, _x))
|
#define C_VAR2(_x, _uniq) C_CONCATENATE(c_internal_var_unique_, C_CONCATENATE(_uniq, _x))
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* C_CC_MACRO1() - provide safe environment to a macro
|
* C_CC_MACRO1() - Provide safe environment to a macro
|
||||||
* @_call: macro to call
|
* @_call: Macro to call
|
||||||
* @_x1: first argument
|
* @_x1: First argument
|
||||||
* @...: further arguments to forward unmodified to @_call
|
* @...: Further arguments to forward unmodified to ``_call``
|
||||||
*
|
*
|
||||||
* This function simplifies the implementation of macros. Whenever you
|
* This function simplifies the implementation of macros. Whenever you
|
||||||
* implement a macro, provide the internal macro name as @_call and its
|
* implement a macro, provide the internal macro name as ``_call`` and its
|
||||||
* argument as @_x1. Inside of your internal macro, you...
|
* argument as ``_x1``. Inside of your internal macro, you...
|
||||||
*
|
*
|
||||||
* - ...are safe against multiple evaluation errors, since C_CC_MACRO1 will
|
* - are safe against multiple evaluation errors, since ``C_CC_MACRO1``
|
||||||
* store the initial parameters in temporary variables.
|
* will store the initial parameters in temporary variables.
|
||||||
*
|
*
|
||||||
* - ...support constant folding, as C_CC_MACRO1 takes care to invoke your
|
* - support constant folding, as ``C_CC_MACRO1`` takes care to invoke your
|
||||||
* macro with the original values, if they are compile-time constant.
|
* macro with the original values, if they are compile-time constant.
|
||||||
*
|
*
|
||||||
* - ...have unique variable names for recursive callers and will not run into
|
* - have unique variable names for recursive callers and will not run into
|
||||||
* variable-shadowing-warnings accidentally.
|
* variable-shadowing-warnings accidentally.
|
||||||
*
|
*
|
||||||
* - ...have properly typed arguments as C_CC_MACRO1 stores the original
|
* - have properly typed arguments as ``C_CC_MACRO1`` stores the original
|
||||||
* arguments in an `__auto_type` temporary variable.
|
* arguments in an ``__auto_type`` temporary variable.
|
||||||
*
|
*
|
||||||
* Return: Result of @_call is returned.
|
* Return: Result of ``_call`` is returned.
|
||||||
*/
|
*/
|
||||||
#define C_CC_MACRO1(_call, _x1, ...) C_INTERNAL_CC_MACRO1(_call, __COUNTER__, (_x1), ## __VA_ARGS__)
|
#define C_CC_MACRO1(_call, _x1, ...) C_INTERNAL_CC_MACRO1(_call, __COUNTER__, (_x1), ## __VA_ARGS__)
|
||||||
#define C_INTERNAL_CC_MACRO1(_call, _x1q, _x1, ...) \
|
#define C_INTERNAL_CC_MACRO1(_call, _x1q, _x1, ...) \
|
||||||
|
@ -193,15 +325,15 @@ _Static_assert(_assertion, _message); \
|
||||||
}))
|
}))
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* C_CC_MACRO2() - provide safe environment to a macro
|
* C_CC_MACRO2() - Provide safe environment to a macro
|
||||||
* @_call: macro to call
|
* @_call: Macro to call
|
||||||
* @_x1: first argument
|
* @_x1: First argument
|
||||||
* @_x2: second argument
|
* @_x2: Second argument
|
||||||
* @...: further arguments to forward unmodified to @_call
|
* @...: Further arguments to forward unmodified to ``_call``
|
||||||
*
|
*
|
||||||
* This is the 2-argument equivalent of C_CC_MACRO1().
|
* This is the 2-argument equivalent of :c:macro:`C_CC_MACRO1()`.
|
||||||
*
|
*
|
||||||
* Return: Result of @_call is returned.
|
* Return: Result of ``_call`` is returned.
|
||||||
*/
|
*/
|
||||||
#define C_CC_MACRO2(_call, _x1, _x2, ...) C_INTERNAL_CC_MACRO2(_call, __COUNTER__, (_x1), __COUNTER__, (_x2), ## __VA_ARGS__)
|
#define C_CC_MACRO2(_call, _x1, _x2, ...) C_INTERNAL_CC_MACRO2(_call, __COUNTER__, (_x1), __COUNTER__, (_x2), ## __VA_ARGS__)
|
||||||
#define C_INTERNAL_CC_MACRO2(_call, _x1q, _x1, _x2q, _x2, ...) \
|
#define C_INTERNAL_CC_MACRO2(_call, _x1q, _x1, _x2q, _x2, ...) \
|
||||||
|
@ -215,16 +347,16 @@ _Static_assert(_assertion, _message); \
|
||||||
}))
|
}))
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* C_CC_MACRO3() - provide safe environment to a macro
|
* C_CC_MACRO3() - Provide safe environment to a macro
|
||||||
* @_call: macro to call
|
* @_call: Macro to call
|
||||||
* @_x1: first argument
|
* @_x1: First argument
|
||||||
* @_x2: second argument
|
* @_x2: Second argument
|
||||||
* @_x3: third argument
|
* @_x3: Third argument
|
||||||
* @...: further arguments to forward unmodified to @_call
|
* @...: Further arguments to forward unmodified to ``_call``
|
||||||
*
|
*
|
||||||
* This is the 3-argument equivalent of C_CC_MACRO1().
|
* This is the 3-argument equivalent of :c:macro:`C_CC_MACRO1()`.
|
||||||
*
|
*
|
||||||
* Return: Result of @_call is returned.
|
* Return: Result of ``_call`` is returned.
|
||||||
*/
|
*/
|
||||||
#define C_CC_MACRO3(_call, _x1, _x2, _x3, ...) C_INTERNAL_CC_MACRO3(_call, __COUNTER__, (_x1), __COUNTER__, (_x2), __COUNTER__, (_x3), ## __VA_ARGS__)
|
#define C_CC_MACRO3(_call, _x1, _x2, _x3, ...) C_INTERNAL_CC_MACRO3(_call, __COUNTER__, (_x1), __COUNTER__, (_x2), __COUNTER__, (_x3), ## __VA_ARGS__)
|
||||||
#define C_INTERNAL_CC_MACRO3(_call, _x1q, _x1, _x2q, _x2, _x3q, _x3, ...) \
|
#define C_INTERNAL_CC_MACRO3(_call, _x1q, _x1, _x2q, _x2, _x3q, _x3, ...) \
|
||||||
|
@ -239,8 +371,17 @@ _Static_assert(_assertion, _message); \
|
||||||
}))
|
}))
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* C_ARRAY_SIZE() - calculate number of array elements at compile time
|
* DOC: Standard Library Utilities
|
||||||
* @_x: array to calculate size of
|
*
|
||||||
|
* The C Standard Library lacks some crucial and basic support functions. This
|
||||||
|
* section describes the set of helpers provided as extension to the standard
|
||||||
|
* library.
|
||||||
|
*/
|
||||||
|
/**/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* C_ARRAY_SIZE() - Calculate number of array elements at compile time
|
||||||
|
* @_x: Array to calculate size of
|
||||||
*
|
*
|
||||||
* Return: Evaluates to a constant integer expression.
|
* Return: Evaluates to a constant integer expression.
|
||||||
*/
|
*/
|
||||||
|
@ -258,9 +399,8 @@ _Static_assert(_assertion, _message); \
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* C_DECIMAL_MAX() - calculate maximum length of the decimal
|
* C_DECIMAL_MAX() - Calculate maximum length of a decimal representation
|
||||||
* representation of an integer
|
* @_type: Integer variable/type
|
||||||
* @_type: integer variable/type
|
|
||||||
*
|
*
|
||||||
* This calculates the bytes required for the decimal representation of an
|
* This calculates the bytes required for the decimal representation of an
|
||||||
* integer of the given type. It accounts for a possible +/- prefix, but it
|
* integer of the given type. It accounts for a possible +/- prefix, but it
|
||||||
|
@ -292,28 +432,41 @@ _Static_assert(_assertion, _message); \
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* c_container_of() - cast a member of a structure out to the containing structure
|
* c_container_of() - Cast a member of a structure out to the containing type
|
||||||
* @_ptr: pointer to the member or NULL
|
* @_ptr: Pointer to the member or NULL
|
||||||
* @_type: type of the container struct this is embedded in
|
* @_type: Type of the container struct this is embedded in
|
||||||
* @_member: name of the member within the struct
|
* @_member: Name of the member within the struct
|
||||||
*
|
*
|
||||||
* This uses `offsetof(3)` to turn a pointer to a structure-member into a
|
* This uses ``offsetof(3)`` to turn a pointer to a structure-member into a
|
||||||
* pointer to the surrounding structure.
|
* pointer to the surrounding structure.
|
||||||
*
|
*
|
||||||
* Return: Pointer to the surrounding object.
|
* Return: Pointer to the surrounding object.
|
||||||
*/
|
*/
|
||||||
#define c_container_of(_ptr, _type, _member) C_CC_MACRO1(C_CONTAINER_OF, (_ptr), _type, _member)
|
#define c_container_of(_ptr, _type, _member) C_CC_MACRO1(C_CONTAINER_OF, (_ptr), _type, _member)
|
||||||
#define C_CONTAINER_OF(_ptr, _type, _member) \
|
#define C_CONTAINER_OF(_ptr, _type, _member) \
|
||||||
__extension__ ({ \
|
C_EXPR_ASSERT( \
|
||||||
/* trigger warning if types do not match */ \
|
(_ptr ? (_type*)c_internal_container_of((void *)_ptr, offsetof(_type, _member)) : NULL), \
|
||||||
(void)(&((_type *)0)->_member == (_ptr)); \
|
__builtin_types_compatible_p( \
|
||||||
_ptr ? (_type*)( (char*)_ptr - offsetof(_type, _member) ) : NULL; \
|
__typeof__(*(_ptr)), \
|
||||||
})
|
__typeof__(((_type){})._member) \
|
||||||
|
) || __builtin_types_compatible_p( \
|
||||||
|
__typeof__(_ptr), \
|
||||||
|
__typeof__(NULL) \
|
||||||
|
), \
|
||||||
|
"Invalid use of C_CONTAINER_OF()" \
|
||||||
|
)
|
||||||
|
static inline void *c_internal_container_of(void *ptr, size_t offset) {
|
||||||
|
/*
|
||||||
|
* Arithmetic on NULL is UB, even if in dead-code. Hide it in a proper
|
||||||
|
* C function, so the macro never emits it as code.
|
||||||
|
*/
|
||||||
|
return (char *)ptr - offset;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* c_max() - compute maximum of two values
|
* c_max() - Compute maximum of two values
|
||||||
* @_a: value A
|
* @_a: Value A
|
||||||
* @_b: value B
|
* @_b: Value B
|
||||||
*
|
*
|
||||||
* Calculate the maximum of both passed values. Both arguments are evaluated
|
* Calculate the maximum of both passed values. Both arguments are evaluated
|
||||||
* exactly once, under all circumstances. Furthermore, if both values are
|
* exactly once, under all circumstances. Furthermore, if both values are
|
||||||
|
@ -329,9 +482,9 @@ _Static_assert(_assertion, _message); \
|
||||||
#define C_MAX(_a, _b) ((_a) > (_b) ? (_a) : (_b))
|
#define C_MAX(_a, _b) ((_a) > (_b) ? (_a) : (_b))
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* c_min() - compute minimum of two values
|
* c_min() - Compute minimum of two values
|
||||||
* @_a: value A
|
* @_a: Value A
|
||||||
* @_b: value B
|
* @_b: Value B
|
||||||
*
|
*
|
||||||
* Calculate the minimum of both passed values. Both arguments are evaluated
|
* Calculate the minimum of both passed values. Both arguments are evaluated
|
||||||
* exactly once, under all circumstances. Furthermore, if both values are
|
* exactly once, under all circumstances. Furthermore, if both values are
|
||||||
|
@ -347,32 +500,32 @@ _Static_assert(_assertion, _message); \
|
||||||
#define C_MIN(_a, _b) ((_a) < (_b) ? (_a) : (_b))
|
#define C_MIN(_a, _b) ((_a) < (_b) ? (_a) : (_b))
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* c_less_by() - calculate clamped difference of two values
|
* c_less_by() - Calculate clamped difference of two values
|
||||||
* @_a: minuend
|
* @_a: Minuend
|
||||||
* @_b: subtrahend
|
* @_b: Subtrahend
|
||||||
*
|
*
|
||||||
* Calculate [_a - _b], but clamp the result to 0. Both arguments are evaluated
|
* Calculate ``_a - _b``, but clamp the result to 0. Both arguments are
|
||||||
* exactly once, under all circumstances. Furthermore, if both values are
|
* evaluated exactly once, under all circumstances. Furthermore, if both values
|
||||||
* constant expressions, the result will be constant as well.
|
* are constant expressions, the result will be constant as well.
|
||||||
*
|
*
|
||||||
* The comparison of their values is performed with the types given by the
|
* The comparison of their values is performed with the types given by the
|
||||||
* caller. It is the caller's responsibility to convert them to suitable types
|
* caller. It is the caller's responsibility to convert them to suitable types
|
||||||
* if necessary.
|
* if necessary.
|
||||||
*
|
*
|
||||||
* Return: This computes [_a - _b], if [_a > _b]. Otherwise, 0 is returned.
|
* Return: This computes ``_a - _b``, if ``_a > _b``. Otherwise, 0 is returned.
|
||||||
*/
|
*/
|
||||||
#define c_less_by(_a, _b) C_CC_MACRO2(C_LESS_BY, (_a), (_b))
|
#define c_less_by(_a, _b) C_CC_MACRO2(C_LESS_BY, (_a), (_b))
|
||||||
#define C_LESS_BY(_a, _b) ((_a) > (_b) ? (_a) - (_b) : 0)
|
#define C_LESS_BY(_a, _b) ((_a) > (_b) ? (_a) - (_b) : 0)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* c_clamp() - clamp value to lower and upper boundary
|
* c_clamp() - Clamp value to lower and upper boundary
|
||||||
* @_x: value to clamp
|
* @_x: Value to clamp
|
||||||
* @_low: lower boundary
|
* @_low: Lower boundary
|
||||||
* @_high: higher boundary
|
* @_high: Higher boundary
|
||||||
*
|
*
|
||||||
* This clamps @_x to the lower and higher bounds given as @_low and @_high.
|
* This clamps ``_x`` to the lower and higher bounds given as ``_low`` and
|
||||||
* All arguments are evaluated exactly once, and yield a constant expression if
|
* ``_high``. All arguments are evaluated exactly once, and yield a constant
|
||||||
* all arguments are constant as well.
|
* expression if all arguments are constant as well.
|
||||||
*
|
*
|
||||||
* The comparison of their values is performed with the types given by the
|
* The comparison of their values is performed with the types given by the
|
||||||
* caller. It is the caller's responsibility to convert them to suitable types
|
* caller. It is the caller's responsibility to convert them to suitable types
|
||||||
|
@ -384,18 +537,18 @@ _Static_assert(_assertion, _message); \
|
||||||
#define C_CLAMP(_x, _low, _high) ((_x) > (_high) ? (_high) : (_x) < (_low) ? (_low) : (_x))
|
#define C_CLAMP(_x, _low, _high) ((_x) > (_high) ? (_high) : (_x) < (_low) ? (_low) : (_x))
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* c_div_round_up() - calculate integer quotient but round up
|
* c_div_round_up() - Calculate integer quotient but round up
|
||||||
* @_x: dividend
|
* @_x: Dividend
|
||||||
* @_y: divisor
|
* @_y: Divisor
|
||||||
*
|
*
|
||||||
* Calculates [x / y] but rounds up the result to the next integer. All
|
* Calculates ``x / y`` but rounds up the result to the next integer. All
|
||||||
* arguments are evaluated exactly once, and yield a constant expression if all
|
* arguments are evaluated exactly once, and yield a constant expression if all
|
||||||
* arguments are constant.
|
* arguments are constant.
|
||||||
*
|
*
|
||||||
* Note:
|
* **Note:**
|
||||||
* [(x + y - 1) / y] suffers from an integer overflow, even though the
|
* ``(x + y - 1) / y`` suffers from an integer overflow, even though the
|
||||||
* computation should be possible in the given type. Therefore, we use
|
* computation should be possible in the given type. Therefore, we use
|
||||||
* [x / y + !!(x % y)]. Note that on most CPUs a division returns both the
|
* ``x / y + !!(x % y)``. Note that on most CPUs a division returns both the
|
||||||
* quotient and the remainder, so both should be equally fast. Furthermore, if
|
* quotient and the remainder, so both should be equally fast. Furthermore, if
|
||||||
* the divisor is a power of two, the compiler will optimize it, anyway.
|
* the divisor is a power of two, the compiler will optimize it, anyway.
|
||||||
*
|
*
|
||||||
|
@ -409,34 +562,35 @@ _Static_assert(_assertion, _message); \
|
||||||
#define C_DIV_ROUND_UP(_x, _y) ((_x) / (_y) + !!((_x) % (_y)))
|
#define C_DIV_ROUND_UP(_x, _y) ((_x) / (_y) + !!((_x) % (_y)))
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* c_align_to() - align value to a multiple
|
* c_align_to() - Align value to a multiple
|
||||||
* @_val: value to align
|
* @_val: Value to align
|
||||||
* @_to: align to multiple of this
|
* @_to: Align to multiple of this
|
||||||
*
|
*
|
||||||
* This aligns @_val to a multiple of @_to. If @_val is already a multiple of
|
* This aligns ``_val`` to a multiple of ``_to``. If ``_val`` is already a
|
||||||
* @_to, @_val is returned unchanged. This function operates within the
|
* multiple of ``_to``, ``_val`` is returned unchanged. This function operates
|
||||||
* boundaries of the type of @_val and @_to. Make sure to cast them if needed.
|
* within the boundaries of the type of ``_val`` and ``_to``. Make sure to cast
|
||||||
|
* them if needed.
|
||||||
*
|
*
|
||||||
* The arguments of this macro are evaluated exactly once. If both arguments
|
* The arguments of this macro are evaluated exactly once. If both arguments
|
||||||
* are a constant expression, this also yields a constant return value.
|
* are a constant expression, this also yields a constant return value.
|
||||||
*
|
*
|
||||||
* Note that @_to must be a power of 2, otherwise the behavior will not match
|
* Note that ``_to`` must be a power of 2, otherwise the behavior will not
|
||||||
* expectations.
|
* match expectations.
|
||||||
*
|
*
|
||||||
* Return: @_val aligned to a multiple of @_to
|
* Return: ``_val`` aligned to a multiple of ``_to``.
|
||||||
*/
|
*/
|
||||||
#define c_align_to(_val, _to) C_CC_MACRO2(C_ALIGN_TO, (_val), (_to))
|
#define c_align_to(_val, _to) C_CC_MACRO2(C_ALIGN_TO, (_val), (_to))
|
||||||
#define C_ALIGN_TO(_val, _to) (((_val) + (_to) - 1) & ~((_to) - 1))
|
#define C_ALIGN_TO(_val, _to) (((_val) + (_to) - 1) & ~((_to) - 1))
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* c_assert() - runtime assertions
|
* c_assert() - Runtime assertions
|
||||||
* @expr_result: result of an expression
|
* @expr_result: Result of an expression
|
||||||
*
|
*
|
||||||
* This function behaves like the standard `assert(3)` macro. That is, if
|
* This function behaves like the standard ``assert(3)`` macro. That is, if
|
||||||
* `NDEBUG` is defined, it is a no-op. In all other cases it will assert that
|
* ``NDEBUG`` is defined, it is a no-op. In all other cases it will assert that
|
||||||
* the result of the passed expression is true.
|
* the result of the passed expression is true.
|
||||||
*
|
*
|
||||||
* Unlike the standard `assert(3)` macro, this function always evaluates its
|
* Unlike the standard ``assert(3)`` macro, this function always evaluates its
|
||||||
* argument. This means side-effects will always be evaluated! However, if the
|
* argument. This means side-effects will always be evaluated! However, if the
|
||||||
* macro is used with constant expressions, the compiler will be able to
|
* macro is used with constant expressions, the compiler will be able to
|
||||||
* optimize it away.
|
* optimize it away.
|
||||||
|
@ -447,13 +601,13 @@ _Static_assert(_assertion, _message); \
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* c_errno() - return valid errno
|
* c_errno() - Return valid errno
|
||||||
*
|
*
|
||||||
* This helper should be used to shut up gcc if you know 'errno' is valid (ie.,
|
* This helper should be used to shut up gcc if you know ``errno`` is valid
|
||||||
* errno is > 0). Instead of "return -errno;", use
|
* (ie., ``errno`` is greater than 0). Instead of ``return -errno;``, use
|
||||||
* "return -c_errno();" It will suppress bogus gcc warnings in case it assumes
|
* ``return -c_errno();`` It will suppress bogus gcc warnings in case it
|
||||||
* 'errno' might be 0 (or <0) and thus the caller's error-handling might not be
|
* assumes ``errno`` might be 0 (or smaller than 0) and thus the caller's
|
||||||
* triggered.
|
* error-handling might not be triggered.
|
||||||
*
|
*
|
||||||
* This helper should be avoided whenever possible. However, occasionally we
|
* This helper should be avoided whenever possible. However, occasionally we
|
||||||
* really want to shut up gcc (especially with static/inline functions). In
|
* really want to shut up gcc (especially with static/inline functions). In
|
||||||
|
@ -470,62 +624,179 @@ static inline int c_errno(void) {
|
||||||
return _c_likely_(errno > 0) ? errno : ENOTRECOVERABLE;
|
return _c_likely_(errno > 0) ? errno : ENOTRECOVERABLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Common Destructors
|
* c_memset() - Fill memory region with constant byte
|
||||||
|
* @p: Pointer to memory region, if non-empty
|
||||||
|
* @c: Value to fill with
|
||||||
|
* @n: Size of the memory region in bytes
|
||||||
*
|
*
|
||||||
* Followingly, there're a bunch of common 'static inline' destructors, which
|
* This function works like ``memset(3)`` if ``n`` is non-zero. If ``n`` is
|
||||||
* simply call the function that they're named after, but return "INVALID"
|
* zero, this function is a no-op. Therefore, unlike ``memset(3)`` it is safe
|
||||||
* instead of "void". This allows direct assignment to any member-field and/or
|
* to call this function with ``NULL`` as ``p`` if ``n`` is 0.
|
||||||
* variable they're defined in, like:
|
|
||||||
*
|
*
|
||||||
* foo = c_free(foo);
|
* Return: ``p`` is returned.
|
||||||
|
*/
|
||||||
|
static inline void *c_memset(void *p, int c, size_t n) {
|
||||||
|
if (n > 0)
|
||||||
|
memset(p, c, n);
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* c_memzero() - Clear memory area
|
||||||
|
* @p: Pointer to memory region, if non-empty
|
||||||
|
* @n: Size of the memory region in bytes
|
||||||
*
|
*
|
||||||
* or
|
* Clear a memory area to 0. If the memory area is empty, this is a no-op.
|
||||||
|
* Similar to ``c_memset()``, this function allows ``p`` to be ``NULL`` if the
|
||||||
|
* area is empty.
|
||||||
*
|
*
|
||||||
* foo->bar = c_close(foo->bar);
|
* Return: ``p`` is returned.
|
||||||
|
*/
|
||||||
|
static inline void *c_memzero(void *p, size_t n) {
|
||||||
|
return c_memset(p, 0, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* c_memcpy() - Copy memory area
|
||||||
|
* @dst: Pointer to target area
|
||||||
|
* @src: Pointer to source area
|
||||||
|
* @n: Length of area to copy
|
||||||
|
*
|
||||||
|
* Copy the memory of size ``n`` from ``src`` to ``dst``, just as ``memcpy(3)``
|
||||||
|
* does, except this function allows either to be ``NULL`` if ``n`` is zero. In
|
||||||
|
* the latter case, the operation is a no-op.
|
||||||
|
*
|
||||||
|
* Return: ``p`` is returned.
|
||||||
|
*/
|
||||||
|
static inline void *c_memcpy(void *dst, const void *src, size_t n) {
|
||||||
|
if (n > 0)
|
||||||
|
memcpy(dst, src, n);
|
||||||
|
return dst;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DOC: Common Destructors
|
||||||
|
*
|
||||||
|
* A set of destructors is provided which extends standard library destructors
|
||||||
|
* to adhere to some adjuvant rules. In particular, they return an invalid
|
||||||
|
* value of the particular object, rather than void. This allows direct
|
||||||
|
* assignment to any member-field and/or variable they are defined in, like:
|
||||||
|
*
|
||||||
|
* .. code-block:: c
|
||||||
|
*
|
||||||
|
* foo = c_free(foo);
|
||||||
|
* foo->bar = c_close(foo->bar);
|
||||||
*
|
*
|
||||||
* Furthermore, all those destructors can be safely called with the "INVALID"
|
* Furthermore, all those destructors can be safely called with the "INVALID"
|
||||||
* value as argument, and they will be a no-op.
|
* value as argument, and they will be a no-op.
|
||||||
*/
|
*/
|
||||||
|
/**/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* c_free() - Destructor-wrapper for free()
|
||||||
|
* @p: Value to pass to destructor, or NULL
|
||||||
|
*
|
||||||
|
* Wrapper around ``free()``, but always returns ``NULL``.
|
||||||
|
*
|
||||||
|
* Return: NULL is returned.
|
||||||
|
*/
|
||||||
static inline void *c_free(void *p) {
|
static inline void *c_free(void *p) {
|
||||||
free(p);
|
free(p);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* c_close() - Destructor-wrapper for close()
|
||||||
|
* @fd: File-descriptor to pass to destructor, or negative value
|
||||||
|
*
|
||||||
|
* Wrapper around ``close()``, but a no-op if a negative value is provided.
|
||||||
|
* Always returns ``-1``.
|
||||||
|
*
|
||||||
|
* Return: -1 is returned.
|
||||||
|
*/
|
||||||
static inline int c_close(int fd) {
|
static inline int c_close(int fd) {
|
||||||
if (fd >= 0)
|
if (fd >= 0)
|
||||||
close(fd);
|
close(fd);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* c_fclose() - Destructor-wrapper for fclose()
|
||||||
|
* @f: File handle to pass to destructor, or NULL
|
||||||
|
*
|
||||||
|
* Wrapper around ``fclose()``, but a no-op if ``NULL`` is passed. Always
|
||||||
|
* returns ``NULL``.
|
||||||
|
*
|
||||||
|
* Return: NULL is returned.
|
||||||
|
*/
|
||||||
static inline FILE *c_fclose(FILE *f) {
|
static inline FILE *c_fclose(FILE *f) {
|
||||||
if (f)
|
if (f)
|
||||||
fclose(f);
|
fclose(f);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* c_closedir() - Destructor-wrapper for closedir)
|
||||||
|
* @d: Directory handle to pass to destructor, or NULL
|
||||||
|
*
|
||||||
|
* Wrapper around ``closedir()``, but a no-op if ``NULL`` is passed. Always
|
||||||
|
* returns ``NULL``.
|
||||||
|
*
|
||||||
|
* Return: NULL is returned.
|
||||||
|
*/
|
||||||
static inline DIR *c_closedir(DIR *d) {
|
static inline DIR *c_closedir(DIR *d) {
|
||||||
if (d)
|
if (d)
|
||||||
closedir(d);
|
closedir(d);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Common Cleanup Helpers
|
* DOC: Common Cleanup Helpers
|
||||||
*
|
*
|
||||||
* A bunch of _c_cleanup_(foobarp) helpers that are used all over the place.
|
* A set of helpers that aid in creating functions suitable for use with
|
||||||
* Note that all of those have the "if (IS_INVALID(foobar))" check inline, so
|
* :c:macro:`_c_cleanup_()`. Furthermore, a collection of predefined cleanup
|
||||||
* compilers can optimize most of the cleanup-paths in a function. However, if
|
* functions of a set of standard library objects ready for use with
|
||||||
* the function they call already does this _inline_, then it might be skipped.
|
* :c:macro:`_c_cleanup_()`.
|
||||||
|
* Those cleanup helpers are always suffixed with a ``p``.
|
||||||
|
*
|
||||||
|
* The helpers that are provided are:
|
||||||
|
*
|
||||||
|
* - ``c_freep()``: Wrapper around :c:func:`c_free()`.
|
||||||
|
* - ``c_closep()``: Wrapper around :c:func:`c_close()`.
|
||||||
|
* - ``c_fclosep()``: Wrapper around :c:func:`c_fclose()`.
|
||||||
|
* - ``c_closedirp()``: Wrapper around :c:func:`c_closedir()`.
|
||||||
*/
|
*/
|
||||||
|
/**/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* C_DEFINE_CLEANUP() - Define cleanup helper
|
||||||
|
* @_type: Type of object to cleanup
|
||||||
|
* @_func: Destructor of the respective type
|
||||||
|
*
|
||||||
|
* Define a C static inline function that takes a single argument of type
|
||||||
|
* `_type` and calls `_func` on it, if its dereferenced value of its argument
|
||||||
|
* evaluates to true. Otherwise, it is a no-op.
|
||||||
|
*
|
||||||
|
* This macro allows for very simple and fast creation of cleanup helpers for
|
||||||
|
* use with ``_c_cleanup_()``, based on any destructor and type you provide to
|
||||||
|
* it.
|
||||||
|
*/
|
||||||
#define C_DEFINE_CLEANUP(_type, _func) \
|
#define C_DEFINE_CLEANUP(_type, _func) \
|
||||||
static inline void _func ## p(_type *p) { \
|
static inline void _func ## p(_type *p) { \
|
||||||
if (*p) \
|
if (*p) \
|
||||||
_func(*p); \
|
_func(*p); \
|
||||||
} struct c_internal_trailing_semicolon
|
} struct c_internal_trailing_semicolon
|
||||||
|
|
||||||
|
/**
|
||||||
|
* C_DEFINE_DIRECT_CLEANUP() - Define direct cleanup helper
|
||||||
|
* @_type: Type of object to cleanup
|
||||||
|
* @_func: Destructor of the respective type
|
||||||
|
*
|
||||||
|
* This works like :c:macro:`C_DEFINE_CLEANUP()` but does not check the
|
||||||
|
* dereferenced value of its argument. It always unconditionally invokes the
|
||||||
|
* destructor.
|
||||||
|
*/
|
||||||
#define C_DEFINE_DIRECT_CLEANUP(_type, _func) \
|
#define C_DEFINE_DIRECT_CLEANUP(_type, _func) \
|
||||||
static inline void _func ## p(_type *p) { \
|
static inline void _func ## p(_type *p) { \
|
||||||
_func(*p); \
|
_func(*p); \
|
||||||
|
|
5
src/docs/api.rst
Normal file
5
src/docs/api.rst
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
API
|
||||||
|
===
|
||||||
|
|
||||||
|
.. c:autodoc:: c-*.h
|
||||||
|
:transform: kerneldoc
|
43
src/docs/conf.py
Normal file
43
src/docs/conf.py
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
#
|
||||||
|
# Sphinx Documentation Configuration
|
||||||
|
#
|
||||||
|
|
||||||
|
import re
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import capidocs.kerneldoc
|
||||||
|
import hawkmoth
|
||||||
|
|
||||||
|
# Global Setup
|
||||||
|
|
||||||
|
project = 'c-stdaux'
|
||||||
|
|
||||||
|
author = 'C-Util Community'
|
||||||
|
copyright = '2022, C-Util Community'
|
||||||
|
|
||||||
|
# Hawkmoth C-Audodoc Setup
|
||||||
|
|
||||||
|
capidocs.kerneldoc.hawkmoth_conf()
|
||||||
|
|
||||||
|
# Extensions
|
||||||
|
|
||||||
|
exclude_patterns = []
|
||||||
|
|
||||||
|
extensions = [
|
||||||
|
'hawkmoth',
|
||||||
|
]
|
||||||
|
|
||||||
|
# Hawkmoth Options
|
||||||
|
|
||||||
|
cautodoc_clang = capidocs.kerneldoc.hawkmoth_include_args()
|
||||||
|
|
||||||
|
cautodoc_root = os.path.abspath('..')
|
||||||
|
|
||||||
|
cautodoc_transformations = {
|
||||||
|
'kerneldoc': capidocs.kerneldoc.hawkmoth_converter,
|
||||||
|
}
|
||||||
|
|
||||||
|
# HTML Options
|
||||||
|
|
||||||
|
html_theme = 'sphinx_rtd_theme'
|
14
src/docs/index.rst
Normal file
14
src/docs/index.rst
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
Introduction
|
||||||
|
============
|
||||||
|
|
||||||
|
The **c-stdaux** project contains support-macros and auxiliary functions around
|
||||||
|
the functionality of common C standard libraries. This includes helpers for the
|
||||||
|
ISO-C Standard Library, but also other common specifications like POSIX or
|
||||||
|
common extended features of wide-spread compilers like gcc and clang.
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:caption: Library Documentation
|
||||||
|
:hidden:
|
||||||
|
|
||||||
|
self
|
||||||
|
api
|
3
src/docs/requirements.txt
Normal file
3
src/docs/requirements.txt
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
c-apidocs>=0.0.2
|
||||||
|
clang>=6
|
||||||
|
hawkmoth>=0.7
|
6
src/libcstdaux.sym
Normal file
6
src/libcstdaux.sym
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
LIBCSTDAUX_1 {
|
||||||
|
global:
|
||||||
|
c_internal_dummy;
|
||||||
|
local:
|
||||||
|
*;
|
||||||
|
};
|
|
@ -4,9 +4,14 @@
|
||||||
# future, if we add more complex helpers.)
|
# future, if we add more complex helpers.)
|
||||||
#
|
#
|
||||||
|
|
||||||
|
libcstdaux_vars = {
|
||||||
|
'cflags': ' '.join(cflags),
|
||||||
|
'version-scripts': use_version_scripts,
|
||||||
|
}
|
||||||
|
|
||||||
libcstdaux_dep = declare_dependency(
|
libcstdaux_dep = declare_dependency(
|
||||||
include_directories: include_directories('.'),
|
include_directories: include_directories('.'),
|
||||||
variables: { 'cflags': ' '.join(cflags) },
|
variables: libcstdaux_vars,
|
||||||
version: meson.project_version(),
|
version: meson.project_version(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -17,6 +22,7 @@ if not meson.is_subproject()
|
||||||
description: project_description,
|
description: project_description,
|
||||||
filebase: 'libcstdaux-'+major,
|
filebase: 'libcstdaux-'+major,
|
||||||
name: 'libcstdaux',
|
name: 'libcstdaux',
|
||||||
|
unescaped_variables: libcstdaux_vars,
|
||||||
version: meson.project_version(),
|
version: meson.project_version(),
|
||||||
)
|
)
|
||||||
endif
|
endif
|
||||||
|
|
|
@ -189,6 +189,9 @@ static void test_api_macros(void) {
|
||||||
static void test_api_functions(void) {
|
static void test_api_functions(void) {
|
||||||
void *fns[] = {
|
void *fns[] = {
|
||||||
(void *)c_errno,
|
(void *)c_errno,
|
||||||
|
(void *)c_memset,
|
||||||
|
(void *)c_memzero,
|
||||||
|
(void *)c_memcpy,
|
||||||
(void *)c_free,
|
(void *)c_free,
|
||||||
(void *)c_close,
|
(void *)c_close,
|
||||||
(void *)c_fclose,
|
(void *)c_fclose,
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
|
|
||||||
#undef NDEBUG
|
#undef NDEBUG
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <sys/eventfd.h>
|
|
||||||
#include "c-stdaux.h"
|
#include "c-stdaux.h"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -138,6 +137,8 @@ static void test_misc(int non_constant_expr) {
|
||||||
c_assert(&sub == c_container_of(&sub.a, struct foobar, a));
|
c_assert(&sub == c_container_of(&sub.a, struct foobar, a));
|
||||||
c_assert(&sub == c_container_of(&sub.b, struct foobar, b));
|
c_assert(&sub == c_container_of(&sub.b, struct foobar, b));
|
||||||
c_assert(&sub == c_container_of((const char *)&sub.b, struct foobar, b));
|
c_assert(&sub == c_container_of((const char *)&sub.b, struct foobar, b));
|
||||||
|
|
||||||
|
c_assert(!c_container_of(NULL, struct foobar, b));
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -304,6 +305,58 @@ static void test_misc(int non_constant_expr) {
|
||||||
errno = 0;
|
errno = 0;
|
||||||
c_assert(c_errno() != errno);
|
c_assert(c_errno() != errno);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Test c_memset(). Simply verify its most basic behavior, as well as
|
||||||
|
* calling it on empty regions.
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
uint64_t v = (uint64_t)-1;
|
||||||
|
size_t n;
|
||||||
|
void *p;
|
||||||
|
|
||||||
|
/* try filling with 0 and 0xff */
|
||||||
|
c_assert(v == (uint64_t)-1);
|
||||||
|
c_memset(&v, 0, sizeof(v));
|
||||||
|
c_assert(v == (uint64_t)0);
|
||||||
|
c_memset(&v, 0xff, sizeof(v));
|
||||||
|
c_assert(v == (uint64_t)-1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Try tricking the optimizer into thinking @p cannot be NULL,
|
||||||
|
* as normal `memset(3)` would allow.
|
||||||
|
*/
|
||||||
|
p = NULL;
|
||||||
|
n = 0;
|
||||||
|
c_memset(p, 0, n);
|
||||||
|
if (p)
|
||||||
|
abort();
|
||||||
|
c_assert(p == NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Test c_memzero(). Simply verify it can clear a trivial area to 0.
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
uint64_t v = (uint64_t)-1;
|
||||||
|
|
||||||
|
c_assert(v == (uint64_t)-1);
|
||||||
|
c_memzero(&v, sizeof(v));
|
||||||
|
c_assert(v == (uint64_t)0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Test c_memcpy() with a simple 8-byte copy.
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
uint64_t v1 = (uint64_t)-1, v2 = (uint64_t)0;
|
||||||
|
|
||||||
|
c_assert(v1 == (uint64_t)-1);
|
||||||
|
c_memcpy(&v1, &v2, sizeof(v1));
|
||||||
|
c_assert(v1 == (uint64_t)0);
|
||||||
|
|
||||||
|
c_memcpy(NULL, NULL, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -345,13 +398,16 @@ static void test_destructors(void) {
|
||||||
* helpers actually close the fd, and cope fine with negative numbers.
|
* helpers actually close the fd, and cope fine with negative numbers.
|
||||||
*/
|
*/
|
||||||
{
|
{
|
||||||
int fd;
|
int r, fd1, fd2, tmp[2];
|
||||||
|
|
||||||
fd = eventfd(0, EFD_CLOEXEC);
|
r = pipe(tmp);
|
||||||
c_assert(fd >= 0);
|
c_assert(r >= 0);
|
||||||
|
fd1 = tmp[0];
|
||||||
|
fd2 = tmp[1];
|
||||||
|
|
||||||
/* verify c_close() returns -1 */
|
/* verify c_close() returns -1 */
|
||||||
c_assert(c_close(fd) == -1);
|
c_assert(c_close(fd1) == -1);
|
||||||
|
c_assert(c_close(fd2) == -1);
|
||||||
|
|
||||||
/* verify c_close() deals fine with negative fds */
|
/* verify c_close() deals fine with negative fds */
|
||||||
c_assert(c_close(-1) == -1);
|
c_assert(c_close(-1) == -1);
|
||||||
|
@ -370,11 +426,15 @@ static void test_destructors(void) {
|
||||||
* path works as well.
|
* path works as well.
|
||||||
*/
|
*/
|
||||||
for (i = 0; i < 2; ++i) {
|
for (i = 0; i < 2; ++i) {
|
||||||
_c_cleanup_(c_closep) _c_unused_ int t = -1;
|
_c_cleanup_(c_closep) _c_unused_ int t1 = -1, t2 = -1;
|
||||||
|
|
||||||
t = eventfd(0, EFD_CLOEXEC);
|
r = pipe(tmp);
|
||||||
c_assert(t >= 0);
|
c_assert(r >= 0);
|
||||||
c_assert(t == fd);
|
t1 = tmp[0];
|
||||||
|
t2 = tmp[1];
|
||||||
|
|
||||||
|
c_assert(t1 == fd1);
|
||||||
|
c_assert(t2 == fd2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -383,11 +443,13 @@ static void test_destructors(void) {
|
||||||
* tests for c_close() (i.e., sparse FD allocation).
|
* tests for c_close() (i.e., sparse FD allocation).
|
||||||
*/
|
*/
|
||||||
{
|
{
|
||||||
|
int r, fd, tmp[2];
|
||||||
FILE *f;
|
FILE *f;
|
||||||
int fd;
|
|
||||||
|
|
||||||
fd = eventfd(0, EFD_CLOEXEC);
|
r = pipe(tmp);
|
||||||
c_assert(fd >= 0);
|
c_assert(r >= 0);
|
||||||
|
fd = tmp[0];
|
||||||
|
c_close(tmp[1]);
|
||||||
|
|
||||||
f = fdopen(fd, "r");
|
f = fdopen(fd, "r");
|
||||||
c_assert(f);
|
c_assert(f);
|
||||||
|
@ -415,10 +477,12 @@ static void test_destructors(void) {
|
||||||
_c_cleanup_(c_fclosep) _c_unused_ FILE *t = NULL;
|
_c_cleanup_(c_fclosep) _c_unused_ FILE *t = NULL;
|
||||||
int tfd;
|
int tfd;
|
||||||
|
|
||||||
tfd = eventfd(0, EFD_CLOEXEC);
|
r = pipe(tmp);
|
||||||
c_assert(tfd >= 0);
|
c_assert(r >= 0);
|
||||||
c_assert(tfd == fd); /* the same as before */
|
tfd = tmp[0];
|
||||||
|
c_close(tmp[1]);
|
||||||
|
|
||||||
|
c_assert(tfd == fd); /* the same as before */
|
||||||
t = fdopen(tfd, "r");
|
t = fdopen(tfd, "r");
|
||||||
c_assert(t);
|
c_assert(t);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue