mirror of
https://github.com/tezc/sc.git
synced 2025-01-28 07:03:06 +08:00
init
This commit is contained in:
commit
5b73822c55
12
.cirrus.yml
Normal file
12
.cirrus.yml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
freebsd_task:
|
||||||
|
only_if: $CIRRUS_BRANCH == 'master'
|
||||||
|
use_compute_credits: $CIRRUS_USER_COLLABORATOR == 'true'
|
||||||
|
freebsd_instance:
|
||||||
|
image_family: freebsd-12-1
|
||||||
|
cpu: 1
|
||||||
|
memory: 2G
|
||||||
|
test_script:
|
||||||
|
- pkg install -y git cmake
|
||||||
|
- mkdir build && cd build
|
||||||
|
- cmake -DSANITIZER=address .. && make -j && make check && rm -rf *
|
||||||
|
- cmake -DSANITIZER=undefined .. && make -j && make check && rm -rf *
|
118
.clang-format
Normal file
118
.clang-format
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
---
|
||||||
|
Language: Cpp
|
||||||
|
AccessModifierOffset: -4
|
||||||
|
AlignAfterOpenBracket: Align
|
||||||
|
AlignConsecutiveAssignments: false
|
||||||
|
AlignConsecutiveDeclarations: false
|
||||||
|
AlignConsecutiveMacros: true
|
||||||
|
AlignEscapedNewlines: Right
|
||||||
|
AlignOperands: Align
|
||||||
|
AlignTrailingComments: true
|
||||||
|
AllowAllArgumentsOnNextLine: false
|
||||||
|
AllowAllParametersOfDeclarationOnNextLine: false
|
||||||
|
AllowShortBlocksOnASingleLine: Never
|
||||||
|
AllowShortCaseLabelsOnASingleLine: false
|
||||||
|
AllowShortFunctionsOnASingleLine: None
|
||||||
|
AllowShortIfStatementsOnASingleLine: Never
|
||||||
|
AllowShortLoopsOnASingleLine: false
|
||||||
|
AlwaysBreakAfterDefinitionReturnType: None
|
||||||
|
AlwaysBreakAfterReturnType: None
|
||||||
|
AlwaysBreakBeforeMultilineStrings: false
|
||||||
|
AlwaysBreakTemplateDeclarations: No
|
||||||
|
BinPackArguments: true
|
||||||
|
BinPackParameters: true
|
||||||
|
|
||||||
|
BraceWrapping:
|
||||||
|
AfterCaseLabel: false
|
||||||
|
AfterClass: false
|
||||||
|
AfterControlStatement: Never
|
||||||
|
AfterEnum: true
|
||||||
|
AfterFunction: true
|
||||||
|
AfterNamespace: false
|
||||||
|
AfterObjCDeclaration: false
|
||||||
|
AfterStruct: true
|
||||||
|
AfterUnion: true
|
||||||
|
AfterExternBlock: false
|
||||||
|
BeforeCatch: false
|
||||||
|
BeforeElse: false
|
||||||
|
IndentBraces: false
|
||||||
|
SplitEmptyFunction: true
|
||||||
|
SplitEmptyRecord: true
|
||||||
|
SplitEmptyNamespace: true
|
||||||
|
BreakBeforeBinaryOperators: None
|
||||||
|
BreakBeforeBraces: Custom
|
||||||
|
BreakBeforeInheritanceComma: false
|
||||||
|
BreakBeforeTernaryOperators: false
|
||||||
|
BreakConstructorInitializersBeforeComma: false
|
||||||
|
BreakConstructorInitializers: BeforeColon
|
||||||
|
BreakStringLiterals: true
|
||||||
|
ColumnLimit: 80
|
||||||
|
CommentPragmas: '^ IWYU pragma:'
|
||||||
|
CompactNamespaces: false
|
||||||
|
ConstructorInitializerAllOnOneLineOrOnePerLine: false
|
||||||
|
ConstructorInitializerIndentWidth: 4
|
||||||
|
ContinuationIndentWidth: 8
|
||||||
|
DerivePointerAlignment: false
|
||||||
|
DisableFormat: false
|
||||||
|
ExperimentalAutoDetectBinPacking: false
|
||||||
|
FixNamespaceComments: true
|
||||||
|
ForEachMacros:
|
||||||
|
- sc_array_foreach
|
||||||
|
- sc_list_foreach
|
||||||
|
- sc_list_foreach_safe
|
||||||
|
- sc_map_foreach
|
||||||
|
- sc_map_foreach_key
|
||||||
|
- sc_map_foreach_value
|
||||||
|
- sc_queue_foreach
|
||||||
|
IncludeBlocks: Regroup
|
||||||
|
IncludeCategories:
|
||||||
|
- Regex: '^("[.a-zA-Z0-9_-]+\.h")'
|
||||||
|
Priority: 1
|
||||||
|
|
||||||
|
# Local headers: "foo/bar.h"
|
||||||
|
- Regex: '^("[.a-zA-Z0-9_/-]+\.h")'
|
||||||
|
Priority: 2
|
||||||
|
|
||||||
|
# C Header: <foo.h>, <net/foo.h>, etc
|
||||||
|
- Regex: '^(<[.a-zA-Z0-9_/-]+\.h>)'
|
||||||
|
Priority: 3
|
||||||
|
|
||||||
|
IncludeIsMainRegex: '[^_test]'
|
||||||
|
IndentPPDirectives: BeforeHash
|
||||||
|
IndentGotoLabels: false
|
||||||
|
IndentCaseBlocks: false
|
||||||
|
IndentCaseLabels: false
|
||||||
|
IndentWidth: 4
|
||||||
|
IndentWrappedFunctionNames: false
|
||||||
|
KeepEmptyLinesAtTheStartOfBlocks: true
|
||||||
|
MacroBlockBegin: ''
|
||||||
|
MacroBlockEnd: ''
|
||||||
|
MaxEmptyLinesToKeep: 2
|
||||||
|
NamespaceIndentation: None
|
||||||
|
PenaltyBreakAssignment: 2
|
||||||
|
PenaltyBreakBeforeFirstCallParameter: 19
|
||||||
|
PenaltyBreakComment: 300
|
||||||
|
PenaltyBreakFirstLessLess: 120
|
||||||
|
PenaltyBreakString: 1000
|
||||||
|
PenaltyExcessCharacter: 1000000
|
||||||
|
PenaltyReturnTypeOnItsOwnLine: 100000000
|
||||||
|
PointerAlignment: Right
|
||||||
|
ReflowComments: true
|
||||||
|
SortIncludes: true
|
||||||
|
SortUsingDeclarations: true
|
||||||
|
SpaceAfterCStyleCast: true
|
||||||
|
SpaceAfterTemplateKeyword: true
|
||||||
|
SpaceBeforeAssignmentOperators: true
|
||||||
|
SpaceBeforeParens: ControlStatements
|
||||||
|
SpaceInEmptyParentheses: false
|
||||||
|
SpacesBeforeTrailingComments: 1
|
||||||
|
SpacesInAngles: false
|
||||||
|
SpacesInContainerLiterals: true
|
||||||
|
SpacesInCStyleCastParentheses: false
|
||||||
|
SpacesInParentheses: false
|
||||||
|
SpacesInSquareBrackets: false
|
||||||
|
Standard: Latest
|
||||||
|
TabWidth: 4
|
||||||
|
UseTab: Never
|
||||||
|
...
|
||||||
|
|
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
*.h linguist-language=C
|
147
.github/workflows/.actions.yml
vendored
Normal file
147
.github/workflows/.actions.yml
vendored
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
on: [ push, pull_request ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
archs:
|
||||||
|
# The host should always be linux
|
||||||
|
runs-on: ubuntu-18.04
|
||||||
|
name: Build on ${{ matrix.distro }} ${{ matrix.arch }}
|
||||||
|
|
||||||
|
# Run steps on a matrix of 3 arch/distro combinations
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- arch: armv6
|
||||||
|
distro: buster
|
||||||
|
- arch: armv7
|
||||||
|
distro: buster
|
||||||
|
- arch: aarch64
|
||||||
|
distro: fedora_latest
|
||||||
|
- arch: ppc64le
|
||||||
|
distro: fedora_latest
|
||||||
|
- arch: s390x
|
||||||
|
distro: fedora_latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2.1.0
|
||||||
|
- uses: uraimo/run-on-arch-action@v2.0.7
|
||||||
|
name: Build artifact
|
||||||
|
id: build
|
||||||
|
with:
|
||||||
|
arch: ${{ matrix.arch }}
|
||||||
|
distro: ${{ matrix.distro }}
|
||||||
|
|
||||||
|
# Not required, but speeds up builds
|
||||||
|
githubToken: ${{ github.token }}
|
||||||
|
|
||||||
|
# Pass some environment variables to the container
|
||||||
|
env: | # YAML, but pipe character is necessary
|
||||||
|
artifact_name: git-${{ matrix.distro }}_${{ matrix.arch }}
|
||||||
|
|
||||||
|
# The shell to run commands with in the container
|
||||||
|
shell: /bin/sh
|
||||||
|
|
||||||
|
# Install some dependencies in the container. This speeds up builds if
|
||||||
|
# you are also using githubToken. Any dependencies installed here will
|
||||||
|
# be part of the container image that gets cached, so subsequent
|
||||||
|
# builds don't have to re-install them. The image layer is cached
|
||||||
|
# publicly in your project's package repository, so it is vital that
|
||||||
|
# no secrets are present in the container state or logs.
|
||||||
|
install: |
|
||||||
|
case "${{ matrix.distro }}" in
|
||||||
|
ubuntu*|jessie|stretch|buster)
|
||||||
|
apt-get update -q -y
|
||||||
|
apt-get install -q -y build-essential git gcc valgrind cmake
|
||||||
|
;;
|
||||||
|
fedora*)
|
||||||
|
dnf -y update
|
||||||
|
dnf -y install git gcc valgrind cmake
|
||||||
|
;;
|
||||||
|
alpine*)
|
||||||
|
apk update
|
||||||
|
apk add libexecinfo-dev linux-headers util-linux alpine-sdk perf libc-dev
|
||||||
|
apk add git gcc clang valgrind cmake
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Produce a binary artifact and place it in the mounted volume
|
||||||
|
run: |
|
||||||
|
uname -a;id;uname -m;lscpu | grep Endian
|
||||||
|
mkdir build && cd build
|
||||||
|
cmake .. && make -j && make check
|
||||||
|
ubuntu:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
name: Build on Ubuntu
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
compiler: [ gcc, clang ]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2.1.0
|
||||||
|
- name: build
|
||||||
|
env:
|
||||||
|
CC: ${{ matrix.compiler }}
|
||||||
|
run: |
|
||||||
|
sudo apt-get install valgrind cmake
|
||||||
|
mkdir build-debug && cd build-debug
|
||||||
|
cmake .. -DSANITIZER=address
|
||||||
|
make -j
|
||||||
|
make check
|
||||||
|
rm -rf *
|
||||||
|
cmake .. -DSANITIZER=undefined
|
||||||
|
make -j
|
||||||
|
make check
|
||||||
|
rm -rf *
|
||||||
|
cmake ..
|
||||||
|
make -j
|
||||||
|
make valgrind
|
||||||
|
macos:
|
||||||
|
runs-on: macos-latest
|
||||||
|
name: Build on Mac OS
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
compiler: [ gcc, clang ]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2.1.0
|
||||||
|
- name: build
|
||||||
|
env:
|
||||||
|
CC: ${{ matrix.compiler }}
|
||||||
|
run: |
|
||||||
|
mkdir build-debug && cd build-debug
|
||||||
|
cmake .. -DSANITIZER=address
|
||||||
|
make -j
|
||||||
|
make check
|
||||||
|
rm -rf *
|
||||||
|
cmake .. -DSANITIZER=undefined
|
||||||
|
make -j
|
||||||
|
make check
|
||||||
|
|
||||||
|
windows:
|
||||||
|
runs-on: windows-latest
|
||||||
|
name: Build on Windows
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2.1.0
|
||||||
|
- name: build
|
||||||
|
run: |
|
||||||
|
mkdir build-debug && cd build-debug
|
||||||
|
cmake -G "Visual Studio 16 2019" -A x64 ..
|
||||||
|
cmake --build .
|
||||||
|
ctest -C Debug
|
||||||
|
coverage:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
name: Coverage
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2.1.0
|
||||||
|
- name: build
|
||||||
|
run: |
|
||||||
|
sudo apt-get install cmake lcov
|
||||||
|
mkdir build-debug && cd build-debug
|
||||||
|
cmake .. -DCMAKE_BUILD_TYPE=Coverage
|
||||||
|
make -j
|
||||||
|
make coverage
|
||||||
|
bash <(curl -s https://codecov.io/bash) -f coverage.info -t ${{ secrets.CODECOV }}
|
56
.gitignore
vendored
Normal file
56
.gitignore
vendored
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
# Prerequisites
|
||||||
|
*.d
|
||||||
|
|
||||||
|
# Object files
|
||||||
|
*.o
|
||||||
|
*.ko
|
||||||
|
*.obj
|
||||||
|
*.elf
|
||||||
|
|
||||||
|
# Linker output
|
||||||
|
*.ilk
|
||||||
|
*.map
|
||||||
|
*.exp
|
||||||
|
|
||||||
|
# Precompiled Headers
|
||||||
|
*.gch
|
||||||
|
*.pch
|
||||||
|
|
||||||
|
# Libraries
|
||||||
|
*.lib
|
||||||
|
*.a
|
||||||
|
*.la
|
||||||
|
*.lo
|
||||||
|
|
||||||
|
# Shared objects (inc. Windows DLLs)
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.so.*
|
||||||
|
*.dylib
|
||||||
|
|
||||||
|
# Executables
|
||||||
|
*.exe
|
||||||
|
*.out
|
||||||
|
*.app
|
||||||
|
*.i*86
|
||||||
|
*.x86_64
|
||||||
|
*.hex
|
||||||
|
|
||||||
|
|
||||||
|
# Debug files
|
||||||
|
*.dSYM/
|
||||||
|
*.su
|
||||||
|
*.idb
|
||||||
|
*.pdb
|
||||||
|
|
||||||
|
# Kernel Module Compile Results
|
||||||
|
*.mod*
|
||||||
|
*.cmd
|
||||||
|
.tmp_versions/
|
||||||
|
modules.order
|
||||||
|
Module.symvers
|
||||||
|
Mkfile.old
|
||||||
|
dkms.conf
|
||||||
|
**/build*
|
||||||
|
**/cmake*
|
||||||
|
*.idea*
|
75
CMakeLists.txt
Normal file
75
CMakeLists.txt
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.5.1)
|
||||||
|
project(tz_lib C)
|
||||||
|
include(CTest)
|
||||||
|
include(CheckCCompilerFlag)
|
||||||
|
|
||||||
|
enable_testing()
|
||||||
|
|
||||||
|
if (NOT CMAKE_BUILD_TYPE)
|
||||||
|
set(CMAKE_BUILD_TYPE "Debug")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
message(STATUS "Build type ${CMAKE_BUILD_TYPE}")
|
||||||
|
|
||||||
|
add_subdirectory(array)
|
||||||
|
add_subdirectory(crc32)
|
||||||
|
add_subdirectory(heap)
|
||||||
|
add_subdirectory(ini)
|
||||||
|
add_subdirectory(linked-list)
|
||||||
|
add_subdirectory(logger)
|
||||||
|
add_subdirectory(map)
|
||||||
|
add_subdirectory(mutex)
|
||||||
|
add_subdirectory(queue)
|
||||||
|
add_subdirectory(perf)
|
||||||
|
add_subdirectory(string)
|
||||||
|
add_subdirectory(time)
|
||||||
|
add_subdirectory(timer)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# --------------------- Test Configuration Start ---------------------------- #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
|
||||||
|
# ----------------------- - Code Coverage Start ----------------------------- #
|
||||||
|
|
||||||
|
if (${CMAKE_BUILD_TYPE} MATCHES "Coverage")
|
||||||
|
if ("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU")
|
||||||
|
add_compile_options(--coverage)
|
||||||
|
link_libraries(gcov)
|
||||||
|
else()
|
||||||
|
message(FATAL_ERROR "Only GCC is supported for coverage")
|
||||||
|
endif()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
add_custom_target(coverage)
|
||||||
|
add_custom_command(
|
||||||
|
TARGET coverage
|
||||||
|
COMMAND lcov --capture --directory ..
|
||||||
|
--output-file coverage.info --rc lcov_branch_coverage=1
|
||||||
|
COMMAND lcov --remove coverage.info '/usr/*' '*example*' '*test*'
|
||||||
|
--output-file coverage.info --rc lcov_branch_coverage=1
|
||||||
|
COMMAND lcov --list coverage.info --rc lcov_branch_coverage=1
|
||||||
|
)
|
||||||
|
|
||||||
|
# -------------------------- Code Coverage End ------------------------------ #
|
||||||
|
|
||||||
|
SET(MEMORYCHECK_COMMAND_OPTIONS
|
||||||
|
"-q --log-fd=2 --trace-children=yes --track-origins=yes \
|
||||||
|
--leak-check=full --show-leak-kinds=all --show-reachable=yes \
|
||||||
|
--error-exitcode=255")
|
||||||
|
|
||||||
|
add_custom_target(valgrind ${CMAKE_COMMAND}
|
||||||
|
-E env CTEST_OUTPUT_ON_FAILURE=1
|
||||||
|
${CMAKE_CTEST_COMMAND} -C $<CONFIG>
|
||||||
|
--overwrite MemoryCheckCommandOptions=${MEMORYCHECK_COMMAND_OPTIONS}
|
||||||
|
--verbose -T memcheck WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||||
|
|
||||||
|
add_custom_target(check ${CMAKE_COMMAND}
|
||||||
|
-E env CTEST_OUTPUT_ON_FAILURE=1
|
||||||
|
${CMAKE_CTEST_COMMAND} -C $<CONFIG> --verbose
|
||||||
|
WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||||
|
|
||||||
|
add_dependencies(coverage check)
|
||||||
|
|
||||||
|
# ----------------------- Test Configuration End ---------------------------- #
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2020 Ozan Tezcan
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
36
README.md
Normal file
36
README.md
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
|
||||||
|
## Standalone C libraries
|
||||||
|
- Each sub-folder is independent, consist one *.c *.h pair, just copy files to your project.
|
||||||
|
|
||||||
|
##### OS
|
||||||
|
- Linux, Windows, macOS : ![.github/workflows/.actions.yml](https://github.com/tezc/simple-c/workflows/.github/workflows/.actions.yml/badge.svg)
|
||||||
|
- FreeBSD : [![Build Status](https://api.cirrus-ci.com/github/tezc/simple-c.svg)](https://cirrus-ci.com/github/tezc/simple-c)
|
||||||
|
[![codecov](https://codecov.io/gh/tezc/simple-c/branch/master/graph/badge.svg)](https://codecov.io/gh/tezc/simple-c)
|
||||||
|
|
||||||
|
|
||||||
|
## Vector
|
||||||
|
|
||||||
|
| | | |
|
||||||
|
|-----------------|-----------------------------------------------------------------------------|
|
||||||
|
| **array** | Generic growable array |
|
||||||
|
| **crc32** | Crc32 implementation contains hardware & software versions |
|
||||||
|
| **heap** | Heap implementation which can be used as minheap/max heap/priority queue |
|
||||||
|
| **ini** | Ini file parser |
|
||||||
|
| **linked-list** | Intrusive linked-list which can be used as queue/dequeue/stack etc. |
|
||||||
|
| **logger** | Logger which is configurable to deliver logs to file/stdout/callback. |
|
||||||
|
| **map** | Generic hashmap |
|
||||||
|
| **mutex** | Mutex wrapper for POSIX and Windows |
|
||||||
|
| **perf** | Simple benchmarking tool for Linux |
|
||||||
|
| **queue** | Generic queue implementation which can be used as queue/dequeue/stack etc. |
|
||||||
|
| **string** | Length prefixed string with a few utility functions |
|
||||||
|
| **time** | Time functions for POSIX and Windows |
|
||||||
|
| **timer** | Hashed timer wheel implementation |
|
||||||
|
|
||||||
|
|
||||||
|
#### OS
|
||||||
|
| | |
|
||||||
|
|---------------------|----------|
|
||||||
|
| Linux |
|
||||||
|
| Windows
|
||||||
|
| MacOS
|
||||||
|
| FreeBSD
|
97
array/CMakeLists.txt
Normal file
97
array/CMakeLists.txt
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.5.1)
|
||||||
|
project(sc_array C)
|
||||||
|
|
||||||
|
set(CMAKE_C_STANDARD 99)
|
||||||
|
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||||
|
set(CMAKE_C_EXTENSIONS OFF)
|
||||||
|
|
||||||
|
add_executable(sc_array array_example.c sc_array.h sc_array.c)
|
||||||
|
|
||||||
|
if (NOT CMAKE_C_COMPILER_ID MATCHES "MSVC")
|
||||||
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -Wall -pedantic -Werror")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# --------------------- Test Configuration Start ---------------------------- #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
|
||||||
|
include(CTest)
|
||||||
|
include(CheckCCompilerFlag)
|
||||||
|
|
||||||
|
enable_testing()
|
||||||
|
|
||||||
|
add_executable(${PROJECT_NAME}_test array_test.c sc_array.c)
|
||||||
|
|
||||||
|
target_compile_options(${PROJECT_NAME}_test PRIVATE -DSC_SIZE_MAX=140000ul)
|
||||||
|
|
||||||
|
message(STATUS "Compiler is ${CMAKE_C_COMPILER_ID}")
|
||||||
|
|
||||||
|
if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||||
|
if ("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR
|
||||||
|
"${CMAKE_C_COMPILER_ID}" STREQUAL "GNU")
|
||||||
|
message(STATUS "Defined SC_HAVE_WRAP")
|
||||||
|
target_compile_options(${PROJECT_NAME}_test PRIVATE -DSC_HAVE_WRAP)
|
||||||
|
target_compile_options(${PROJECT_NAME}_test PRIVATE -fno-builtin)
|
||||||
|
target_link_options(${PROJECT_NAME}_test PRIVATE -Wl,--wrap=realloc)
|
||||||
|
endif ()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
if ("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR
|
||||||
|
"${CMAKE_C_COMPILER_ID}" STREQUAL "AppleClang" OR
|
||||||
|
"${CMAKE_C_COMPILER_ID}" STREQUAL "GNU")
|
||||||
|
|
||||||
|
target_compile_options(${PROJECT_NAME}_test PRIVATE -fno-omit-frame-pointer)
|
||||||
|
|
||||||
|
if (SANITIZER)
|
||||||
|
target_compile_options(${PROJECT_NAME}_test PRIVATE -fsanitize=${SANITIZER})
|
||||||
|
target_link_options(${PROJECT_NAME}_test PRIVATE -fsanitize=${SANITIZER})
|
||||||
|
endif ()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
add_test(NAME ${PROJECT_NAME}_test COMMAND ${PROJECT_NAME}_test)
|
||||||
|
|
||||||
|
SET(MEMORYCHECK_COMMAND_OPTIONS
|
||||||
|
"-q --log-fd=2 --trace-children=yes --track-origins=yes \
|
||||||
|
--leak-check=full --show-leak-kinds=all --show-reachable=yes \
|
||||||
|
--error-exitcode=255")
|
||||||
|
|
||||||
|
add_custom_target(valgrind_${PROJECT_NAME} ${CMAKE_COMMAND}
|
||||||
|
-E env CTEST_OUTPUT_ON_FAILURE=1
|
||||||
|
${CMAKE_CTEST_COMMAND} -C $<CONFIG>
|
||||||
|
--overwrite MemoryCheckCommandOptions=${MEMORYCHECK_COMMAND_OPTIONS}
|
||||||
|
--verbose -T memcheck WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||||
|
|
||||||
|
add_custom_target(check_${PROJECT_NAME} ${CMAKE_COMMAND}
|
||||||
|
-E env CTEST_OUTPUT_ON_FAILURE=1
|
||||||
|
${CMAKE_CTEST_COMMAND} -C $<CONFIG> --verbose
|
||||||
|
WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||||
|
|
||||||
|
# ----------------------- - Code Coverage Start ----------------------------- #
|
||||||
|
|
||||||
|
if (${CMAKE_BUILD_TYPE} MATCHES "Coverage")
|
||||||
|
if ("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU")
|
||||||
|
target_compile_options(${PROJECT_NAME}_test PRIVATE --coverage)
|
||||||
|
target_link_libraries(${PROJECT_NAME}_test gcov)
|
||||||
|
else()
|
||||||
|
message(FATAL_ERROR "Only GCC is supported for coverage")
|
||||||
|
endif()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
add_custom_target(coverage_${PROJECT_NAME})
|
||||||
|
add_custom_command(
|
||||||
|
TARGET coverage_${PROJECT_NAME}
|
||||||
|
COMMAND lcov --capture --directory ..
|
||||||
|
--output-file coverage.info --rc lcov_branch_coverage=1
|
||||||
|
COMMAND lcov --remove coverage.info '/usr/*' '*example*' '*test*'
|
||||||
|
--output-file coverage.info --rc lcov_branch_coverage=1
|
||||||
|
COMMAND lcov --list coverage.info --rc lcov_branch_coverage=1
|
||||||
|
)
|
||||||
|
|
||||||
|
add_dependencies(coverage_${PROJECT_NAME} check_${PROJECT_NAME})
|
||||||
|
|
||||||
|
# -------------------------- Code Coverage End ------------------------------ #
|
||||||
|
|
||||||
|
|
||||||
|
# ----------------------- Test Configuration End ---------------------------- #
|
||||||
|
|
68
array/README.md
Normal file
68
array/README.md
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
# Generic array
|
||||||
|
|
||||||
|
#### Overview
|
||||||
|
|
||||||
|
- Type generic array which grows when you add elements.
|
||||||
|
- Index access is possible (e.g float* arr; 'printf("%f", arr[i]')).
|
||||||
|
- Just copy <b>sc_array.h</b> and <b>sc_array.c</b> to your project.
|
||||||
|
|
||||||
|
|
||||||
|
##### Usage
|
||||||
|
|
||||||
|
|
||||||
|
```c
|
||||||
|
int *p;
|
||||||
|
|
||||||
|
sc_array_create(p, 0);
|
||||||
|
|
||||||
|
sc_array_add(p, 0);
|
||||||
|
sc_array_add(p, 1);
|
||||||
|
sc_array_add(p, 3);
|
||||||
|
|
||||||
|
printf("\nRemoving first element \n\n");
|
||||||
|
sc_array_remove(p, 0);
|
||||||
|
|
||||||
|
printf("Capacity %zu \n", sc_array_cap(p));
|
||||||
|
printf("Element count %zu \n", sc_array_size(p));
|
||||||
|
|
||||||
|
|
||||||
|
// Simple loop
|
||||||
|
for (int i = 0; i < sc_array_size(p); i++) {
|
||||||
|
printf("Elem = %d \n", p[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_array_destroy(p);
|
||||||
|
```
|
||||||
|
#### Internals
|
||||||
|
|
||||||
|
##### Memory
|
||||||
|
- Single allocation for all the data.
|
||||||
|
- Lazy allocation. No memory allocation until first 'add'.
|
||||||
|
|
||||||
|
##### Performance
|
||||||
|
- As all the items are in a single contiguous memory, it gives the best
|
||||||
|
performance you can expect.
|
||||||
|
|
||||||
|
##### Note
|
||||||
|
|
||||||
|
Array pointer is not stable. If you pass the array to another function which
|
||||||
|
can add items, do it by passing reference of the array pointer :
|
||||||
|
|
||||||
|
```c
|
||||||
|
void some_function_to_add_elems(long **p)
|
||||||
|
{
|
||||||
|
sc_array_add(*p, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
long *p;
|
||||||
|
|
||||||
|
sc_array_create(p, 0);
|
||||||
|
sc_array_add(p, 300);
|
||||||
|
|
||||||
|
some_function_to_add_elems(&p);
|
||||||
|
sc_array_destroy(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
29
array/array_example.c
Normal file
29
array/array_example.c
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
#include "sc_array.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
int *p;
|
||||||
|
|
||||||
|
sc_array_create(p, 0);
|
||||||
|
|
||||||
|
sc_array_add(p, 0);
|
||||||
|
sc_array_add(p, 1);
|
||||||
|
sc_array_add(p, 3);
|
||||||
|
|
||||||
|
printf("\nRemoving first element \n\n");
|
||||||
|
sc_array_remove(p, 0);
|
||||||
|
|
||||||
|
printf("Capacity %zu \n", sc_array_cap(p));
|
||||||
|
printf("Element count %zu \n", sc_array_size(p));
|
||||||
|
|
||||||
|
// Simple loop
|
||||||
|
for (int i = 0; i < sc_array_size(p); i++) {
|
||||||
|
printf("Elem = %d \n", p[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_array_destroy(p);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
174
array/array_test.c
Normal file
174
array/array_test.c
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
#include "sc_array.h"
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
int example()
|
||||||
|
{
|
||||||
|
int *p;
|
||||||
|
|
||||||
|
sc_array_create(p, 0);
|
||||||
|
|
||||||
|
sc_array_add(p, 0);
|
||||||
|
sc_array_add(p, 1);
|
||||||
|
sc_array_add(p, 3);
|
||||||
|
|
||||||
|
printf("\nRemoving first element \n\n");
|
||||||
|
sc_array_remove(p, 0);
|
||||||
|
|
||||||
|
printf("Capacity %zu \n", sc_array_cap(p));
|
||||||
|
printf("Element count %zu \n", sc_array_size(p));
|
||||||
|
|
||||||
|
for (int i = 0; i < sc_array_size(p); i++) {
|
||||||
|
printf("Elem = %d \n", p[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_array_destroy(p);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int compare(const void *a, const void *b)
|
||||||
|
{
|
||||||
|
const int *x = a;
|
||||||
|
const int *y = b;
|
||||||
|
|
||||||
|
return *x - *y;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test1(void)
|
||||||
|
{
|
||||||
|
int *arr, total = 0;
|
||||||
|
|
||||||
|
sc_array_create(arr, 5);
|
||||||
|
sc_array_add(arr, 3);
|
||||||
|
sc_array_add(arr, 4);
|
||||||
|
sc_array_add(arr, 5);
|
||||||
|
|
||||||
|
assert(sc_array_size(arr) == 3);
|
||||||
|
|
||||||
|
sc_array_remove(arr, 0);
|
||||||
|
assert(arr[0] == 4);
|
||||||
|
sc_array_remove_last(arr);
|
||||||
|
assert(arr[0] == 4);
|
||||||
|
|
||||||
|
sc_array_add(arr, 1);
|
||||||
|
sc_array_add(arr, 3);
|
||||||
|
sc_array_add(arr, 2);
|
||||||
|
sc_array_add(arr, 0);
|
||||||
|
|
||||||
|
sc_array_sort(arr, compare);
|
||||||
|
|
||||||
|
for (int i = 0; i < sc_array_size(arr); i++) {
|
||||||
|
total += arr[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(total == 10);
|
||||||
|
|
||||||
|
for (int i = 0; i < sc_array_size(arr); i++) {
|
||||||
|
assert(arr[i] == i);
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_array_destroy(arr);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef SC_HAVE_WRAP
|
||||||
|
|
||||||
|
bool fail_realloc = false;
|
||||||
|
void *__real_realloc(void *p, size_t size);
|
||||||
|
void *__wrap_realloc(void *p, size_t n)
|
||||||
|
{
|
||||||
|
if (fail_realloc) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return __real_realloc(p, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
void fail_test()
|
||||||
|
{
|
||||||
|
int *arr, total = 0;
|
||||||
|
|
||||||
|
assert(sc_array_create(arr, SIZE_MAX) == false);
|
||||||
|
assert(arr == NULL);
|
||||||
|
assert(sc_array_create(arr, 0) == true);
|
||||||
|
assert(arr != NULL);
|
||||||
|
sc_array_destroy(arr);
|
||||||
|
assert(arr == NULL);
|
||||||
|
assert(sc_array_create(arr, 0) == true);
|
||||||
|
|
||||||
|
size_t count = SC_SIZE_MAX / sizeof(*arr);
|
||||||
|
bool success = false;
|
||||||
|
|
||||||
|
for (int i = 0; i < count + 5; i++) {
|
||||||
|
success = sc_array_add(arr, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(!success);
|
||||||
|
|
||||||
|
sc_array_destroy(arr);
|
||||||
|
sc_array_create(arr, 0);
|
||||||
|
assert(sc_array_size(arr) == 0);
|
||||||
|
|
||||||
|
fail_realloc = true;
|
||||||
|
success = sc_array_add(arr, 0);
|
||||||
|
assert(!success);
|
||||||
|
|
||||||
|
fail_realloc = false;
|
||||||
|
success = sc_array_add(arr, 222);
|
||||||
|
assert(success);
|
||||||
|
sc_array_destroy(arr);
|
||||||
|
|
||||||
|
fail_realloc = true;
|
||||||
|
assert(sc_array_create(arr, 222) == false);
|
||||||
|
fail_realloc = false;
|
||||||
|
|
||||||
|
assert(sc_array_create(arr, 0) == true);
|
||||||
|
fail_realloc = true;
|
||||||
|
success = sc_array_add(arr, 222);
|
||||||
|
assert(!success);
|
||||||
|
fail_realloc = false;
|
||||||
|
|
||||||
|
sc_array_add(arr, 3);
|
||||||
|
sc_array_add(arr, 4);
|
||||||
|
sc_array_add(arr, 5);
|
||||||
|
|
||||||
|
assert(sc_array_size(arr) == 3);
|
||||||
|
|
||||||
|
sc_array_remove(arr, 0);
|
||||||
|
assert(arr[0] == 4);
|
||||||
|
sc_array_remove_last(arr);
|
||||||
|
assert(arr[0] == 4);
|
||||||
|
|
||||||
|
sc_array_add(arr, 1);
|
||||||
|
sc_array_add(arr, 3);
|
||||||
|
sc_array_add(arr, 2);
|
||||||
|
sc_array_add(arr, 0);
|
||||||
|
|
||||||
|
sc_array_sort(arr, compare);
|
||||||
|
|
||||||
|
for (int i = 0; i < sc_array_size(arr); i++) {
|
||||||
|
total += arr[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(total == 10);
|
||||||
|
|
||||||
|
for (int i = 0; i < sc_array_size(arr); i++) {
|
||||||
|
assert(arr[i] == i);
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_array_destroy(arr);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
void fail_test(void)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
example();
|
||||||
|
test1();
|
||||||
|
fail_test();
|
||||||
|
}
|
108
array/sc_array.c
Normal file
108
array/sc_array.c
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020 Ozan Tezcan
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "sc_array.h"
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#ifndef SC_SIZE_MAX
|
||||||
|
#define SC_SIZE_MAX SIZE_MAX
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Empty array instance.
|
||||||
|
* Zero element arrays point at it to avoid initial allocation, so unused
|
||||||
|
* arrays will not allocate memory.
|
||||||
|
*/
|
||||||
|
static const struct sc_array sc_empty = {.size = 0, .cap = 0};
|
||||||
|
|
||||||
|
bool sc_array_init(void **arr, size_t elem_size, size_t cap)
|
||||||
|
{
|
||||||
|
struct sc_array *meta;
|
||||||
|
|
||||||
|
if (cap == 0) {
|
||||||
|
*arr = (void *) sc_empty.elems;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check overflow
|
||||||
|
if (cap > SC_SIZE_MAX / elem_size) {
|
||||||
|
*arr = NULL;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
meta = sc_array_realloc(NULL, sizeof(*meta) + (elem_size * cap));
|
||||||
|
if (meta == NULL) {
|
||||||
|
*arr = NULL;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
meta->size = 0;
|
||||||
|
meta->cap = cap;
|
||||||
|
*arr = meta->elems;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sc_array_term(void **arr)
|
||||||
|
{
|
||||||
|
struct sc_array *meta = sc_array_meta(*arr);
|
||||||
|
|
||||||
|
if (meta != &sc_empty) {
|
||||||
|
sc_array_free(meta);
|
||||||
|
}
|
||||||
|
|
||||||
|
*arr = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sc_array_expand(void **arr, size_t elem_size)
|
||||||
|
{
|
||||||
|
size_t size, cap;
|
||||||
|
struct sc_array *prev, *meta = sc_array_meta(*arr);
|
||||||
|
|
||||||
|
if (meta->size == meta->cap) {
|
||||||
|
|
||||||
|
// Check overflow
|
||||||
|
if (meta->cap > SC_SIZE_MAX / elem_size / 2) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
size = meta->size;
|
||||||
|
cap = (meta != &sc_empty) ? meta->cap * 2 : 2;
|
||||||
|
prev = (meta != &sc_empty) ? meta : NULL;
|
||||||
|
|
||||||
|
meta = sc_array_realloc(prev, sizeof(*meta) + (elem_size * cap));
|
||||||
|
if (meta == NULL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
meta->size = size;
|
||||||
|
meta->cap = cap;
|
||||||
|
*arr = meta->elems;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
169
array/sc_array.h
Normal file
169
array/sc_array.h
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020 Ozan Tezcan
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef SC_ARRAY_H
|
||||||
|
#define SC_ARRAY_H
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <memory.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internals, do not use
|
||||||
|
*/
|
||||||
|
struct sc_array
|
||||||
|
{
|
||||||
|
size_t size;
|
||||||
|
size_t cap;
|
||||||
|
uint8_t elems[];
|
||||||
|
};
|
||||||
|
|
||||||
|
#define sc_array_meta(arr) \
|
||||||
|
((struct sc_array *) ((char *) (arr) -offsetof(struct sc_array, elems)))
|
||||||
|
|
||||||
|
bool sc_array_init(void **arr, size_t elem_size, size_t cap);
|
||||||
|
void sc_array_term(void **arr);
|
||||||
|
bool sc_array_expand(void **arr, size_t elem_size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal End.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure memory allocators here. You can plug your allocator if you want,
|
||||||
|
* replace 'realloc' and 'free' with your allocator, make sure you include
|
||||||
|
* new allocator header.
|
||||||
|
*/
|
||||||
|
#define sc_array_realloc realloc
|
||||||
|
#define sc_array_free free
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param arr Array pointer
|
||||||
|
* @param cap Initial capacity, '0' is accepted, then no memory allocation
|
||||||
|
* will be made until first 'add' operation.
|
||||||
|
* @return 'true' on success, 'false' on out of memory
|
||||||
|
*/
|
||||||
|
#define sc_array_create(arr, cap) \
|
||||||
|
sc_array_init((void **) &(arr), sizeof(*(arr)), cap)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param arr Array pointer
|
||||||
|
*/
|
||||||
|
#define sc_array_destroy(arr) sc_array_term((void **) &(arr));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param arr Array pointer
|
||||||
|
* @return Current allocated capacity
|
||||||
|
*/
|
||||||
|
#define sc_array_cap(arr) (sc_array_meta((arr))->cap)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param arr Array pointer
|
||||||
|
* @return Current element count
|
||||||
|
*/
|
||||||
|
#define sc_array_size(arr) (sc_array_meta((arr))->size)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes items from the list without deallocating underlying memory
|
||||||
|
*
|
||||||
|
* @param arr Array pointer
|
||||||
|
*/
|
||||||
|
#define sc_array_clear(arr) (sc_array_meta((arr))->size = 0)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param v Array pointer
|
||||||
|
* @param elem Element to be appended
|
||||||
|
* @return 'true' on success, 'false' if memory allocation fails if we try
|
||||||
|
* to expand underlying memory.
|
||||||
|
*/
|
||||||
|
#define sc_array_add(arr, elem) \
|
||||||
|
sc_array_expand((void **) &((arr)), sizeof(*(arr))) == true ? \
|
||||||
|
(arr)[sc_array_meta(arr)->size++] = (elem), \
|
||||||
|
true : false
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the element at index i, moves elements to fill the space
|
||||||
|
* unless removed element is the last element
|
||||||
|
*
|
||||||
|
* vec[a,b,c,d,e,f] -> sc_array_remove(vec, 2) - > vec[a,b,d,f,e]
|
||||||
|
*
|
||||||
|
* @param arr Array pointer
|
||||||
|
* @param i Element index to be removed
|
||||||
|
*
|
||||||
|
* If 'i' is out of the range, result is undefined.
|
||||||
|
*/
|
||||||
|
#define sc_array_remove(arr, i) \
|
||||||
|
do { \
|
||||||
|
assert((i) < sc_array_meta(arr)->size); \
|
||||||
|
const size_t to_move = sc_array_size(arr) - (i) -1; \
|
||||||
|
if (to_move > 0) { \
|
||||||
|
memmove(&(arr)[i], &(arr)[(i) + 1], to_move * sizeof(*(arr))); \
|
||||||
|
} \
|
||||||
|
sc_array_meta((arr))->size--; \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes an element at index i, replaces last element with removed element
|
||||||
|
* unless removed element is the last element. This is faster than moving
|
||||||
|
* elements but elements will no longer be in the push order
|
||||||
|
*
|
||||||
|
* arr[a,b,c,d,e,f] -> sc_array_remove_unordered(vec, 2) - > arr[a,b,f,d,e]
|
||||||
|
*
|
||||||
|
* @param arr Array pointer
|
||||||
|
* @param i Element index to be removed
|
||||||
|
*
|
||||||
|
* If 'i' is out of the range, result is undefined.
|
||||||
|
*/
|
||||||
|
#define sc_array_remove_unordered(arr, i) \
|
||||||
|
do { \
|
||||||
|
assert((i) < sc_array_meta(arr)->size); \
|
||||||
|
(arr)[i] = (arr)[(--sc_array_meta((arr))->size)]; \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the last element. If there is no element, result is undefined.
|
||||||
|
* @param arr Array pointer
|
||||||
|
*/
|
||||||
|
#define sc_array_remove_last(arr) \
|
||||||
|
do { \
|
||||||
|
assert(sc_array_meta(arr)->size != 0); \
|
||||||
|
sc_array_meta(arr)->size--; \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sorts the array using qsort()
|
||||||
|
* @param arr Array pointer
|
||||||
|
* @param cmp Comparator, check qsort docs online for details
|
||||||
|
*/
|
||||||
|
#define sc_array_sort(arr, cmp) \
|
||||||
|
(qsort((arr), sc_array_size((arr)), sizeof(*(arr)), cmp))
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
83
crc32/CMakeLists.txt
Normal file
83
crc32/CMakeLists.txt
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.5.1)
|
||||||
|
project(sc_crc32 C)
|
||||||
|
|
||||||
|
set(CMAKE_C_STANDARD 99)
|
||||||
|
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||||
|
set(CMAKE_C_EXTENSIONS OFF)
|
||||||
|
|
||||||
|
add_executable(sc_crc32 crc32_example.c sc_crc32.h sc_crc32.c)
|
||||||
|
|
||||||
|
if (NOT CMAKE_C_COMPILER_ID MATCHES "MSVC")
|
||||||
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -Wall -pedantic -Werror")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# --------------------- Test Configuration Start ---------------------------- #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
|
||||||
|
include(CTest)
|
||||||
|
include(CheckCCompilerFlag)
|
||||||
|
|
||||||
|
enable_testing()
|
||||||
|
|
||||||
|
add_executable(${PROJECT_NAME}_test crc32_test.c sc_crc32.c)
|
||||||
|
|
||||||
|
if ("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR
|
||||||
|
"${CMAKE_C_COMPILER_ID}" STREQUAL "AppleClang" OR
|
||||||
|
"${CMAKE_C_COMPILER_ID}" STREQUAL "GNU")
|
||||||
|
|
||||||
|
target_compile_options(${PROJECT_NAME}_test PRIVATE -fno-omit-frame-pointer)
|
||||||
|
|
||||||
|
if (SANITIZER)
|
||||||
|
target_compile_options(${PROJECT_NAME}_test PRIVATE -fsanitize=${SANITIZER})
|
||||||
|
target_link_options(${PROJECT_NAME}_test PRIVATE -fsanitize=${SANITIZER})
|
||||||
|
endif ()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
add_test(NAME ${PROJECT_NAME}_test COMMAND ${PROJECT_NAME}_test)
|
||||||
|
|
||||||
|
SET(MEMORYCHECK_COMMAND_OPTIONS
|
||||||
|
"-q --log-fd=2 --trace-children=yes --track-origins=yes \
|
||||||
|
--leak-check=full --show-leak-kinds=all --show-reachable=yes \
|
||||||
|
--error-exitcode=255")
|
||||||
|
|
||||||
|
add_custom_target(valgrind_${PROJECT_NAME} ${CMAKE_COMMAND}
|
||||||
|
-E env CTEST_OUTPUT_ON_FAILURE=1
|
||||||
|
${CMAKE_CTEST_COMMAND} -C $<CONFIG>
|
||||||
|
--overwrite MemoryCheckCommandOptions=${MEMORYCHECK_COMMAND_OPTIONS}
|
||||||
|
--verbose -T memcheck WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||||
|
|
||||||
|
add_custom_target(check_${PROJECT_NAME} ${CMAKE_COMMAND}
|
||||||
|
-E env CTEST_OUTPUT_ON_FAILURE=1
|
||||||
|
${CMAKE_CTEST_COMMAND} -C $<CONFIG> --verbose
|
||||||
|
WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||||
|
|
||||||
|
# ----------------------- - Code Coverage Start ----------------------------- #
|
||||||
|
|
||||||
|
if (${CMAKE_BUILD_TYPE} MATCHES "Coverage")
|
||||||
|
if ("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU")
|
||||||
|
target_compile_options(${PROJECT_NAME}_test PRIVATE --coverage)
|
||||||
|
target_link_libraries(${PROJECT_NAME}_test gcov)
|
||||||
|
else()
|
||||||
|
message(FATAL_ERROR "Only GCC is supported for coverage")
|
||||||
|
endif()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
add_custom_target(coverage_${PROJECT_NAME})
|
||||||
|
add_custom_command(
|
||||||
|
TARGET coverage_${PROJECT_NAME}
|
||||||
|
COMMAND lcov --capture --directory ..
|
||||||
|
--output-file coverage.info --rc lcov_branch_coverage=1
|
||||||
|
COMMAND lcov --remove coverage.info '/usr/*' '*example*' '*test*'
|
||||||
|
--output-file coverage.info --rc lcov_branch_coverage=1
|
||||||
|
COMMAND lcov --list coverage.info --rc lcov_branch_coverage=1
|
||||||
|
)
|
||||||
|
|
||||||
|
add_dependencies(coverage_${PROJECT_NAME} check_${PROJECT_NAME})
|
||||||
|
|
||||||
|
# -------------------------- Code Coverage End ------------------------------ #
|
||||||
|
|
||||||
|
|
||||||
|
# ----------------------- Test Configuration End ---------------------------- #
|
||||||
|
|
54
crc32/README.md
Normal file
54
crc32/README.md
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
# CRC32c function
|
||||||
|
|
||||||
|
- Same code from : https://stackoverflow.com/a/17646775
|
||||||
|
- Fixed some alignment issues, replaced asm code with compiler intrinsics
|
||||||
|
- Just copy <b>sc_crc32.h</b> and <b>sc_crc32.c</b> to your project.
|
||||||
|
|
||||||
|
- Compile time switch to hardware version if supported (crc32c instruction on x64),
|
||||||
|
fallback to software version if not available
|
||||||
|
- See CmakeLists.txt, it just checks "-msse4.2" flag. Stackoverflow answer has
|
||||||
|
runtime dispatch between hardware and software versions if you'd like that.
|
||||||
|
|
||||||
|
|
||||||
|
- This is Crc32<b>c</b> algorithm, not Crc32
|
||||||
|
- A faster version might be possible with 'PCLMULQDQ' instruction as explained here
|
||||||
|
|
||||||
|
[[link]](https://www.intel.com/content/dam/www/public/us/en/documents/white-papers/crc-iscsi-polynomial-crc32-instruction-paper.pdf)
|
||||||
|
|
||||||
|
|
||||||
|
```cmake
|
||||||
|
## Cmake
|
||||||
|
|
||||||
|
## Only use hardware version in 64 bit architectures.
|
||||||
|
if("${CMAKE_SIZEOF_VOID_P}" EQUAL "8")
|
||||||
|
check_c_compiler_flag(-msse4.2 HAVE_CRC32_HARDWARE)
|
||||||
|
if (${HAVE_CRC32_HARDWARE})
|
||||||
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -msse4.2 -DSC_HAVE_CRC32_HARDWARE")
|
||||||
|
endif ()
|
||||||
|
endif()
|
||||||
|
```
|
||||||
|
|
||||||
|
```c
|
||||||
|
#include "sc_crc32.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
uint32_t crc;
|
||||||
|
const uint8_t buf[100] = {0};
|
||||||
|
|
||||||
|
sc_crc32_global_init();
|
||||||
|
|
||||||
|
// Partial calculation example
|
||||||
|
crc = sc_crc32(0, buf, 10);
|
||||||
|
crc = sc_crc32(crc, buf + 10, sizeof(buf) - 10);
|
||||||
|
printf("crc : %u \n", crc);
|
||||||
|
|
||||||
|
// Calculate at once
|
||||||
|
crc = sc_crc32(0, buf, sizeof(buf));
|
||||||
|
printf("crc : %u \n", crc);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
22
crc32/crc32_example.c
Normal file
22
crc32/crc32_example.c
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
#include "sc_crc32.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
uint32_t crc;
|
||||||
|
const uint8_t buf[100] = {0};
|
||||||
|
|
||||||
|
sc_crc32_global_init();
|
||||||
|
|
||||||
|
// Partial calculation example
|
||||||
|
crc = sc_crc32(0, buf, 10);
|
||||||
|
crc = sc_crc32(crc, buf + 10, sizeof(buf) - 10);
|
||||||
|
printf("crc : %u \n", crc);
|
||||||
|
|
||||||
|
// Calculate at once
|
||||||
|
crc = sc_crc32(0, buf, sizeof(buf));
|
||||||
|
printf("crc : %u \n", crc);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
24
crc32/crc32_test.c
Normal file
24
crc32/crc32_test.c
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
#include "sc_crc32.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
uint32_t crc1, crc2, crc3;
|
||||||
|
uint8_t buf[128] = {1, 1, 2, 3};
|
||||||
|
uint8_t buf2[4096 * 8] = {2 , 5, 6 ,5};
|
||||||
|
|
||||||
|
sc_crc32_global_init();
|
||||||
|
|
||||||
|
crc1 = sc_crc32(0, buf, 100);
|
||||||
|
crc2 = sc_crc32(crc1, buf + 100, 28);
|
||||||
|
crc3 = sc_crc32(0, buf, 128);
|
||||||
|
|
||||||
|
assert(crc2 == crc3);
|
||||||
|
|
||||||
|
crc1 = sc_crc32(0, buf2, 4096 * 4);
|
||||||
|
crc2 = sc_crc32(crc1, buf2 + (4096 * 4), 4096 * 4);
|
||||||
|
crc3 = sc_crc32(0, buf2, 4096 * 8);
|
||||||
|
|
||||||
|
assert(crc2 == crc3);
|
||||||
|
}
|
339
crc32/sc_crc32.c
Normal file
339
crc32/sc_crc32.c
Normal file
@ -0,0 +1,339 @@
|
|||||||
|
/* crc32c.c -- compute CRC-32C using the Intel crc32 instruction
|
||||||
|
* Copyright (C) 2013 Mark Adler
|
||||||
|
* Version 1.1 1 Aug 2013 Mark Adler
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the author be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Permission is granted to anyone to use this software for any purpose,
|
||||||
|
including commercial applications, and to alter it and redistribute it
|
||||||
|
freely, subject to the following restrictions:
|
||||||
|
|
||||||
|
1. The origin of this software must not be misrepresented; you must not
|
||||||
|
claim that you wrote the original software. If you use this software
|
||||||
|
in a product, an acknowledgment in the product documentation would be
|
||||||
|
appreciated but is not required.
|
||||||
|
2. Altered source versions must be plainly marked as such, and must not be
|
||||||
|
misrepresented as being the original software.
|
||||||
|
3. This notice may not be removed or altered from any source distribution.
|
||||||
|
|
||||||
|
Mark Adler
|
||||||
|
madler@alumni.caltech.edu
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Use hardware CRC instruction on Intel SSE 4.2 processors. This computes a
|
||||||
|
CRC-32C, *not* the CRC-32 used by Ethernet and zip, gzip, etc. A software
|
||||||
|
version is provided as a fall-back, as well as for speed comparisons. */
|
||||||
|
|
||||||
|
/* Version history:
|
||||||
|
1.0 10 Feb 2013 First version
|
||||||
|
1.1 1 Aug 2013 Correct comments on why three crc instructions in parallel
|
||||||
|
1.2 2020 Added gcc intrinsics, fixed alignment issues.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <memory.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/* CRC-32C (iSCSI) polynomial in reversed bit order. */
|
||||||
|
#define CRC32_POLY 0x82f63b78
|
||||||
|
|
||||||
|
#ifdef SC_HAVE_CRC32_HARDWARE
|
||||||
|
#include <x86intrin.h>
|
||||||
|
|
||||||
|
/* Multiply a matrix times a vector over the Galois field of two elements,
|
||||||
|
GF(2). Each element is a bit in an unsigned integer. mat must have at
|
||||||
|
least as many entries as the power of two for most significant one bit in
|
||||||
|
vec. */
|
||||||
|
static inline uint32_t gf2_matrix_times(uint32_t *mat, uint32_t vec)
|
||||||
|
{
|
||||||
|
uint32_t sum = 0;
|
||||||
|
|
||||||
|
while (vec) {
|
||||||
|
if (vec & 1) {
|
||||||
|
sum ^= *mat;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec >>= 1;
|
||||||
|
mat++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Multiply a matrix by itself over GF(2). Both mat and square must have 32
|
||||||
|
rows. */
|
||||||
|
static inline void gf2_matrix_square(uint32_t *square, uint32_t *mat)
|
||||||
|
{
|
||||||
|
for (int n = 0; n < 32; n++) {
|
||||||
|
square[n] = gf2_matrix_times(mat, mat[n]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Construct an operator to apply len zeros to a crc. len must be a power of
|
||||||
|
two. If len is not a power of two, then the result is the same as for the
|
||||||
|
largest power of two less than len. The result for len == 0 is the same as
|
||||||
|
for len == 1. A version of this routine could be easily written for any
|
||||||
|
len, but that is not needed for this application. */
|
||||||
|
static void crc32_zeros_op(uint32_t *even, size_t len)
|
||||||
|
{
|
||||||
|
uint32_t row = 1;
|
||||||
|
uint32_t odd[32]; /* odd-power-of-two zeros operator */
|
||||||
|
|
||||||
|
/* put operator for one zero bit in odd */
|
||||||
|
odd[0] = CRC32_POLY; /* CRC-32C polynomial */
|
||||||
|
|
||||||
|
for (int n = 1; n < 32; n++) {
|
||||||
|
odd[n] = row;
|
||||||
|
row <<= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* put operator for two zero bits in even */
|
||||||
|
gf2_matrix_square(even, odd);
|
||||||
|
|
||||||
|
/* put operator for four zero bits in odd */
|
||||||
|
gf2_matrix_square(odd, even);
|
||||||
|
|
||||||
|
/* first square will put the operator for one zero byte (eight zero bits),
|
||||||
|
in even -- next square puts operator for two zero bytes in odd, and so
|
||||||
|
on, until len has been rotated down to zero */
|
||||||
|
do {
|
||||||
|
gf2_matrix_square(even, odd);
|
||||||
|
len >>= 1;
|
||||||
|
if (len == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
gf2_matrix_square(odd, even);
|
||||||
|
len >>= 1;
|
||||||
|
} while (len);
|
||||||
|
|
||||||
|
/* answer ended up in odd -- copy to even */
|
||||||
|
for (int n = 0; n < 32; n++) {
|
||||||
|
even[n] = odd[n];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Take a length and build four lookup tables for applying the zeros operator
|
||||||
|
for that length, byte-by-byte on the operand. */
|
||||||
|
static void crc32_zeros(uint32_t zeros[][256], size_t len)
|
||||||
|
{
|
||||||
|
uint32_t op[32];
|
||||||
|
|
||||||
|
crc32_zeros_op(op, len);
|
||||||
|
|
||||||
|
for (uint32_t n = 0; n < 256; n++) {
|
||||||
|
zeros[0][n] = gf2_matrix_times(op, n);
|
||||||
|
zeros[1][n] = gf2_matrix_times(op, n << 8);
|
||||||
|
zeros[2][n] = gf2_matrix_times(op, n << 16);
|
||||||
|
zeros[3][n] = gf2_matrix_times(op, n << 24);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Apply the zeros operator table to crc. */
|
||||||
|
static inline uint32_t crc32_shift(uint32_t zeros[][256], uint32_t crc)
|
||||||
|
{
|
||||||
|
return zeros[0][(crc >> 0) & 0xff] ^ zeros[1][(crc >> 8) & 0xff] ^
|
||||||
|
zeros[2][(crc >> 16) & 0xff] ^ zeros[3][(crc >> 24) & 0xff];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Block sizes for three-way parallel crc computation. LONG and SHORT must
|
||||||
|
both be powers of two. The associated string constants must be set
|
||||||
|
accordingly, for use in constructing the assembler instructions. */
|
||||||
|
#define CRC32_LONG 2048
|
||||||
|
#define CRC32_SHORT 256
|
||||||
|
|
||||||
|
static uint32_t crc32c_long[4][256];
|
||||||
|
static uint32_t crc32c_short[4][256];
|
||||||
|
|
||||||
|
static void crc32_init_hw(void)
|
||||||
|
{
|
||||||
|
crc32_zeros(crc32c_long, CRC32_LONG);
|
||||||
|
crc32_zeros(crc32c_short, CRC32_SHORT);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t crc32_hw(uint32_t crc, const uint8_t *buf, uint32_t len)
|
||||||
|
{
|
||||||
|
const unsigned char *next = buf;
|
||||||
|
const unsigned char *end;
|
||||||
|
uint64_t crc0, crc1, crc2; /* need to be 64 bits for crc32q */
|
||||||
|
|
||||||
|
/* pre-process the crc */
|
||||||
|
crc0 = crc ^ 0xffffffff;
|
||||||
|
|
||||||
|
/* compute the crc for up to seven leading bytes to bring the data pointer
|
||||||
|
to an eight-byte boundary */
|
||||||
|
while (len && ((uintptr_t) next & 7) != 0) {
|
||||||
|
crc0 = _mm_crc32_u8(crc0, *next);
|
||||||
|
next++;
|
||||||
|
len--;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* compute the crc on sets of LONG*3 bytes, executing three independent crc
|
||||||
|
instructions, each on LONG bytes -- this is optimized for the Nehalem,
|
||||||
|
Westmere, Sandy Bridge, and Ivy Bridge architectures, which have a
|
||||||
|
throughput of one crc per cycle, but a latency of three cycles */
|
||||||
|
while (len >= CRC32_LONG * 3) {
|
||||||
|
crc1 = 0;
|
||||||
|
crc2 = 0;
|
||||||
|
|
||||||
|
end = next + CRC32_LONG;
|
||||||
|
do {
|
||||||
|
uint64_t a, b, c;
|
||||||
|
|
||||||
|
memcpy(&a, next, 8);
|
||||||
|
memcpy(&b, next + CRC32_LONG, 8);
|
||||||
|
memcpy(&c, next + (CRC32_LONG * 2), 8);
|
||||||
|
|
||||||
|
crc0 = _mm_crc32_u64(crc0, a);
|
||||||
|
crc1 = _mm_crc32_u64(crc1, b);
|
||||||
|
crc2 = _mm_crc32_u64(crc2, c);
|
||||||
|
|
||||||
|
next += 8;
|
||||||
|
} while (next < end);
|
||||||
|
|
||||||
|
crc0 = crc32_shift(crc32c_long, crc0) ^ crc1;
|
||||||
|
crc0 = crc32_shift(crc32c_long, crc0) ^ crc2;
|
||||||
|
|
||||||
|
next += (CRC32_LONG * 2);
|
||||||
|
len -= (CRC32_LONG * 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* do the same thing, but now on SHORT * 3 blocks for the remaining data
|
||||||
|
less than a LONG * 3 block */
|
||||||
|
while (len >= CRC32_SHORT * 3) {
|
||||||
|
crc1 = 0;
|
||||||
|
crc2 = 0;
|
||||||
|
|
||||||
|
end = next + CRC32_SHORT;
|
||||||
|
do {
|
||||||
|
uint64_t a, b, c;
|
||||||
|
|
||||||
|
memcpy(&a, next, 8);
|
||||||
|
memcpy(&b, next + CRC32_SHORT, 8);
|
||||||
|
memcpy(&c, next + (CRC32_SHORT * 2), 8);
|
||||||
|
|
||||||
|
crc0 = _mm_crc32_u64(crc0, a);
|
||||||
|
crc1 = _mm_crc32_u64(crc1, b);
|
||||||
|
crc2 = _mm_crc32_u64(crc2, c);
|
||||||
|
|
||||||
|
next += 8;
|
||||||
|
} while (next < end);
|
||||||
|
|
||||||
|
crc0 = crc32_shift(crc32c_short, crc0) ^ crc1;
|
||||||
|
crc0 = crc32_shift(crc32c_short, crc0) ^ crc2;
|
||||||
|
|
||||||
|
next += (CRC32_SHORT * 2);
|
||||||
|
len -= (CRC32_SHORT * 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* compute the crc on the remaining eight-byte units less than a SHORT * 3
|
||||||
|
block */
|
||||||
|
end = next + (len - (len & 7));
|
||||||
|
while (next < end) {
|
||||||
|
uint64_t a;
|
||||||
|
|
||||||
|
memcpy(&a, next, 8);
|
||||||
|
crc0 = _mm_crc32_u64(crc0, a);
|
||||||
|
next += 8;
|
||||||
|
}
|
||||||
|
len &= 7;
|
||||||
|
|
||||||
|
/* compute the crc for up to seven trailing bytes */
|
||||||
|
while (len) {
|
||||||
|
crc0 = _mm_crc32_u8(crc0, *next);
|
||||||
|
next++;
|
||||||
|
len--;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* return a post-processed crc */
|
||||||
|
return (uint32_t) crc0 ^ 0xffffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
/* Table for a quadword-at-a-time software crc. */
|
||||||
|
static uint32_t crc32c_table[8][256];
|
||||||
|
|
||||||
|
/* Construct table for software CRC-32C calculation. */
|
||||||
|
static void crc32_init_sw(void)
|
||||||
|
{
|
||||||
|
for (uint32_t n = 0; n < 256; n++) {
|
||||||
|
uint32_t crc = n;
|
||||||
|
|
||||||
|
crc = crc & 1 ? (crc >> 1) ^ CRC32_POLY : crc >> 1;
|
||||||
|
crc = crc & 1 ? (crc >> 1) ^ CRC32_POLY : crc >> 1;
|
||||||
|
crc = crc & 1 ? (crc >> 1) ^ CRC32_POLY : crc >> 1;
|
||||||
|
crc = crc & 1 ? (crc >> 1) ^ CRC32_POLY : crc >> 1;
|
||||||
|
crc = crc & 1 ? (crc >> 1) ^ CRC32_POLY : crc >> 1;
|
||||||
|
crc = crc & 1 ? (crc >> 1) ^ CRC32_POLY : crc >> 1;
|
||||||
|
crc = crc & 1 ? (crc >> 1) ^ CRC32_POLY : crc >> 1;
|
||||||
|
crc = crc & 1 ? (crc >> 1) ^ CRC32_POLY : crc >> 1;
|
||||||
|
|
||||||
|
crc32c_table[0][n] = crc;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint32_t n = 0; n < 256; n++) {
|
||||||
|
uint32_t crc = crc32c_table[0][n];
|
||||||
|
|
||||||
|
for (uint32_t k = 1; k < 8; k++) {
|
||||||
|
crc = crc32c_table[0][crc & 0xff] ^ (crc >> 8);
|
||||||
|
crc32c_table[k][n] = crc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Table-driven software version as a fall-back. This is about 15 times slower
|
||||||
|
than using the hardware instructions. This assumes little-endian integers,
|
||||||
|
as is the case on Intel processors that the assembler code here is for. */
|
||||||
|
static uint32_t crc32_sw(uint32_t crci, const void *buf, size_t len)
|
||||||
|
{
|
||||||
|
const unsigned char *next = buf;
|
||||||
|
uint64_t crc = crci ^ 0xffffffff;
|
||||||
|
|
||||||
|
while (len && ((uintptr_t) next & 7) != 0) {
|
||||||
|
crc = crc32c_table[0][(crc ^ *next++) & 0xff] ^ (crc >> 8);
|
||||||
|
len--;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (len >= 8) {
|
||||||
|
crc ^= *(uint64_t *) next;
|
||||||
|
crc = crc32c_table[7][crc & 0xff] ^ crc32c_table[6][(crc >> 8) & 0xff] ^
|
||||||
|
crc32c_table[5][(crc >> 16) & 0xff] ^
|
||||||
|
crc32c_table[4][(crc >> 24) & 0xff] ^
|
||||||
|
crc32c_table[3][(crc >> 32) & 0xff] ^
|
||||||
|
crc32c_table[2][(crc >> 40) & 0xff] ^
|
||||||
|
crc32c_table[1][(crc >> 48) & 0xff] ^ crc32c_table[0][crc >> 56];
|
||||||
|
next += 8;
|
||||||
|
len -= 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (len) {
|
||||||
|
crc = crc32c_table[0][(crc ^ *next++) & 0xff] ^ (crc >> 8);
|
||||||
|
len--;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (uint32_t) crc ^ 0xffffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
uint32_t sc_crc32(uint32_t crc, const uint8_t *buf, uint32_t len)
|
||||||
|
{
|
||||||
|
#ifdef SC_HAVE_CRC32_HARDWARE
|
||||||
|
return crc32_hw(crc, buf, len);
|
||||||
|
#else
|
||||||
|
return crc32_sw(crc, buf, len);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void sc_crc32_global_init()
|
||||||
|
{
|
||||||
|
#ifdef SC_HAVE_CRC32_HARDWARE
|
||||||
|
crc32_init_hw();
|
||||||
|
#else
|
||||||
|
crc32_init_sw();
|
||||||
|
#endif
|
||||||
|
}
|
34
crc32/sc_crc32.h
Normal file
34
crc32/sc_crc32.h
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020 Ozan Tezcan
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef SC_CRC32_H
|
||||||
|
#define SC_CRC32_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
void sc_crc32_global_init(void);
|
||||||
|
|
||||||
|
uint32_t sc_crc32(uint32_t crc, const uint8_t *buf, uint32_t len);
|
||||||
|
|
||||||
|
#endif
|
94
heap/CMakeLists.txt
Normal file
94
heap/CMakeLists.txt
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.5.1)
|
||||||
|
project(sc_heap C)
|
||||||
|
|
||||||
|
set(CMAKE_C_STANDARD 99)
|
||||||
|
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||||
|
set(CMAKE_C_EXTENSIONS OFF)
|
||||||
|
|
||||||
|
add_executable(sc_heap heap_example.c sc_heap.h sc_heap.c)
|
||||||
|
|
||||||
|
if (NOT CMAKE_C_COMPILER_ID MATCHES "MSVC")
|
||||||
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -pedantic -Werror -D_GNU_SOURCE")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# --------------------- Test Configuration Start ---------------------------- #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
|
||||||
|
include(CTest)
|
||||||
|
include(CheckCCompilerFlag)
|
||||||
|
|
||||||
|
enable_testing()
|
||||||
|
|
||||||
|
add_executable(${PROJECT_NAME}_test heap_test.c sc_heap.c)
|
||||||
|
|
||||||
|
target_compile_options(${PROJECT_NAME}_test PRIVATE -DSC_SIZE_MAX=140000ul)
|
||||||
|
|
||||||
|
if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||||
|
if ("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR
|
||||||
|
"${CMAKE_C_COMPILER_ID}" STREQUAL "GNU")
|
||||||
|
target_compile_options(${PROJECT_NAME}_test PRIVATE -DSC_HAVE_WRAP)
|
||||||
|
target_compile_options(${PROJECT_NAME}_test PRIVATE -fno-builtin)
|
||||||
|
target_link_options(${PROJECT_NAME}_test PRIVATE
|
||||||
|
-Wl,--wrap=malloc,--wrap=realloc)
|
||||||
|
endif ()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
if ("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR
|
||||||
|
"${CMAKE_C_COMPILER_ID}" STREQUAL "AppleClang" OR
|
||||||
|
"${CMAKE_C_COMPILER_ID}" STREQUAL "GNU")
|
||||||
|
target_compile_options(${PROJECT_NAME}_test PRIVATE -fno-omit-frame-pointer)
|
||||||
|
|
||||||
|
if (SANITIZER)
|
||||||
|
target_compile_options(${PROJECT_NAME}_test PRIVATE -fsanitize=${SANITIZER})
|
||||||
|
target_link_options(${PROJECT_NAME}_test PRIVATE -fsanitize=${SANITIZER})
|
||||||
|
endif ()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
add_test(NAME ${PROJECT_NAME}_test COMMAND ${PROJECT_NAME}_test)
|
||||||
|
|
||||||
|
SET(MEMORYCHECK_COMMAND_OPTIONS
|
||||||
|
"-q --log-fd=2 --trace-children=yes --track-origins=yes \
|
||||||
|
--leak-check=full --show-leak-kinds=all --show-reachable=yes \
|
||||||
|
--error-exitcode=255")
|
||||||
|
|
||||||
|
add_custom_target(valgrind_${PROJECT_NAME} ${CMAKE_COMMAND}
|
||||||
|
-E env CTEST_OUTPUT_ON_FAILURE=1
|
||||||
|
${CMAKE_CTEST_COMMAND} -C $<CONFIG>
|
||||||
|
--overwrite MemoryCheckCommandOptions=${MEMORYCHECK_COMMAND_OPTIONS}
|
||||||
|
--verbose -T memcheck WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||||
|
|
||||||
|
add_custom_target(check_${PROJECT_NAME} ${CMAKE_COMMAND}
|
||||||
|
-E env CTEST_OUTPUT_ON_FAILURE=1
|
||||||
|
${CMAKE_CTEST_COMMAND} -C $<CONFIG> --verbose
|
||||||
|
WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||||
|
|
||||||
|
# ----------------------- - Code Coverage Start ----------------------------- #
|
||||||
|
|
||||||
|
if (${CMAKE_BUILD_TYPE} MATCHES "Coverage")
|
||||||
|
if ("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU")
|
||||||
|
target_compile_options(${PROJECT_NAME}_test PRIVATE --coverage)
|
||||||
|
target_link_libraries(${PROJECT_NAME}_test gcov)
|
||||||
|
else()
|
||||||
|
message(FATAL_ERROR "Only GCC is supported for coverage")
|
||||||
|
endif()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
add_custom_target(coverage_${PROJECT_NAME})
|
||||||
|
add_custom_command(
|
||||||
|
TARGET coverage_${PROJECT_NAME}
|
||||||
|
COMMAND lcov --capture --directory ..
|
||||||
|
--output-file coverage.info --rc lcov_branch_coverage=1
|
||||||
|
COMMAND lcov --remove coverage.info '/usr/*' '*example*' '*test*'
|
||||||
|
--output-file coverage.info --rc lcov_branch_coverage=1
|
||||||
|
COMMAND lcov --list coverage.info --rc lcov_branch_coverage=1
|
||||||
|
)
|
||||||
|
|
||||||
|
add_dependencies(coverage_${PROJECT_NAME} check_${PROJECT_NAME})
|
||||||
|
|
||||||
|
# -------------------------- Code Coverage End ------------------------------ #
|
||||||
|
|
||||||
|
|
||||||
|
# ----------------------- Test Configuration End ---------------------------- #
|
||||||
|
|
78
heap/README.md
Normal file
78
heap/README.md
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
# Heap
|
||||||
|
|
||||||
|
### Overview
|
||||||
|
|
||||||
|
- Min-heap implementation, it can be used as Max-heap/priority queue as well.
|
||||||
|
- Just copy <b>sc_heap.h</b> and <b>sc_heap.c</b> to your project.
|
||||||
|
|
||||||
|
#### Usage
|
||||||
|
|
||||||
|
|
||||||
|
```c
|
||||||
|
|
||||||
|
#include "sc_heap.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
struct data
|
||||||
|
{
|
||||||
|
int priority;
|
||||||
|
char *data;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct data n[] = {{1, "first"},
|
||||||
|
{4, "fourth"},
|
||||||
|
{5, "fifth"},
|
||||||
|
{3, "third"},
|
||||||
|
{2, "second"}};
|
||||||
|
|
||||||
|
int64_t key;
|
||||||
|
void *data;
|
||||||
|
struct sc_heap heap;
|
||||||
|
|
||||||
|
sc_heap_init(&heap, 0);
|
||||||
|
|
||||||
|
// Min-heap usage
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
sc_heap_add(&heap, n[i].priority, n[i].data);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (sc_heap_pop(&heap, &key, &data)) {
|
||||||
|
printf("key = %ld, data = %s \n", key, (char *) data);
|
||||||
|
}
|
||||||
|
printf("---------------- \n");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Max-heap usage, negate when adding into heap
|
||||||
|
* and negate back after pop :
|
||||||
|
*/
|
||||||
|
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
sc_heap_add(&heap, -(n[i].priority), n[i].data);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (sc_heap_pop(&heap, &key, &data)) {
|
||||||
|
printf("key = %ld, data = %s \n", -key, (char *) data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
####Internals
|
||||||
|
##### Memory
|
||||||
|
|
||||||
|
- All items are on a single contiguous memory.
|
||||||
|
- Lazy allocation is possible. (No memory allocation until the first item)
|
||||||
|
|
||||||
|
##### Performance
|
||||||
|
|
||||||
|
- Heaps are not the fastest data structures as they require unpredictable
|
||||||
|
branches on add/extract operations. Intentionally, only 'void*' values are
|
||||||
|
allowed, so this not a 'generic' data structure. Placing larger values only
|
||||||
|
will worsen this implementation's performance. An item is 16 bytes in the
|
||||||
|
heap and operating on a contiguous memory gives us acceptable performance
|
||||||
|
for some use cases.
|
50
heap/heap_example.c
Normal file
50
heap/heap_example.c
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
#include "sc_heap.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
struct data
|
||||||
|
{
|
||||||
|
int priority;
|
||||||
|
char *data;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct data n[] = {{1, "first"},
|
||||||
|
{4, "fourth"},
|
||||||
|
{5, "fifth"},
|
||||||
|
{3, "third"},
|
||||||
|
{2, "second"}};
|
||||||
|
|
||||||
|
int64_t key;
|
||||||
|
void *data;
|
||||||
|
struct sc_heap heap;
|
||||||
|
|
||||||
|
sc_heap_init(&heap, 0);
|
||||||
|
|
||||||
|
// Min-heap usage
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
sc_heap_add(&heap, n[i].priority, n[i].data);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (sc_heap_pop(&heap, &key, &data)) {
|
||||||
|
printf("key = %ld, data = %s \n", (long int) key, (char *) data);
|
||||||
|
}
|
||||||
|
printf("---------------- \n");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Max-heap usage, negate when adding into heap
|
||||||
|
* and negate back after pop :
|
||||||
|
*/
|
||||||
|
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
sc_heap_add(&heap, -(n[i].priority), n[i].data);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (sc_heap_pop(&heap, &key, &data)) {
|
||||||
|
printf("key = %ld, data = %s \n", (long int) -key, (char *) data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
234
heap/heap_test.c
Normal file
234
heap/heap_test.c
Normal file
@ -0,0 +1,234 @@
|
|||||||
|
#include "sc_heap.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
int example(void)
|
||||||
|
{
|
||||||
|
struct data
|
||||||
|
{
|
||||||
|
int priority;
|
||||||
|
char *data;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct data n[] = {{1, "first"},
|
||||||
|
{4, "fourth"},
|
||||||
|
{5, "fifth"},
|
||||||
|
{3, "third"},
|
||||||
|
{2, "second"}};
|
||||||
|
int64_t key;
|
||||||
|
void *data;
|
||||||
|
struct sc_heap heap;
|
||||||
|
|
||||||
|
sc_heap_init(&heap, 0);
|
||||||
|
|
||||||
|
// Min-heap usage
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
sc_heap_add(&heap, n[i].priority, n[i].data);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (sc_heap_pop(&heap, &key, &data)) {
|
||||||
|
printf("key = %d, data = %s \n", (int) key, (char *) data);
|
||||||
|
}
|
||||||
|
printf("---------------- \n");
|
||||||
|
|
||||||
|
// Max-heap usage, negate when adding into heap and negate back after pop :
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
sc_heap_add(&heap, -(n[i].priority), n[i].data);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (sc_heap_pop(&heap, &key, &data)) {
|
||||||
|
printf("key = %d, data = %s \n", (int) -key, (char *) data);
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_heap_term(&heap);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void test1(void)
|
||||||
|
{
|
||||||
|
int64_t key;
|
||||||
|
void *data;
|
||||||
|
struct sc_heap heap;
|
||||||
|
|
||||||
|
assert(sc_heap_init(&heap, SIZE_MAX / 2) == false);
|
||||||
|
assert(sc_heap_init(&heap, 3) == true);
|
||||||
|
|
||||||
|
for (int i = 0; i < 1000; i++) {
|
||||||
|
assert(sc_heap_add(&heap, i, (void *) (uintptr_t) i) == true);
|
||||||
|
assert(sc_heap_pop(&heap, &key, &data) == true);
|
||||||
|
assert(key == (uintptr_t) data);
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t arr[] = {1, 0, 4, 5, 7, 9, 8, 6, 3, 2};
|
||||||
|
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
assert(sc_heap_add(&heap, arr[i], (void *) (uintptr_t)(arr[i] * 2)) ==
|
||||||
|
true);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
assert(sc_heap_pop(&heap, &key, &data) == true);
|
||||||
|
assert(key == i);
|
||||||
|
assert((uintptr_t) data == i * 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_heap_term(&heap);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test2(void)
|
||||||
|
{
|
||||||
|
static const int64_t arr[] = {1, 0, 4, 5, 7, 9, 8, 6, 3, 2};
|
||||||
|
int64_t key;
|
||||||
|
void *data;
|
||||||
|
struct sc_heap heap;
|
||||||
|
|
||||||
|
assert(sc_heap_init(&heap, 0) == true);
|
||||||
|
|
||||||
|
for (int i = 0; i < 1000; i++) {
|
||||||
|
assert(sc_heap_add(&heap, i, (void *) (uintptr_t) i) == true);
|
||||||
|
assert(sc_heap_pop(&heap, &key, &data) == true);
|
||||||
|
assert(key == (uintptr_t) data);
|
||||||
|
assert(key == (uintptr_t) i);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
assert(sc_heap_add(&heap, -arr[i], (void *) (uintptr_t)(arr[i] * 2)) ==
|
||||||
|
true);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
assert(sc_heap_pop(&heap, &key, &data) == true);
|
||||||
|
assert(-key == 9 - i);
|
||||||
|
assert((uintptr_t) data == (9 - i) * 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_heap_term(&heap);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test3(void)
|
||||||
|
{
|
||||||
|
int64_t key;
|
||||||
|
int arr[100];
|
||||||
|
void *data;
|
||||||
|
struct sc_heap heap;
|
||||||
|
|
||||||
|
assert(sc_heap_init(&heap, 2) == true);
|
||||||
|
assert(sc_heap_add(&heap, 9, (void *) (uintptr_t) 9) == true);
|
||||||
|
assert(sc_heap_peek(&heap, &key, &data) == true);
|
||||||
|
assert(key == 9);
|
||||||
|
assert(data == (void *) (uintptr_t) 9);
|
||||||
|
assert(sc_heap_pop(&heap, &key, &data) == true);
|
||||||
|
assert(key == 9);
|
||||||
|
assert(data == (void *) (uintptr_t) 9);
|
||||||
|
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
arr[i] = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
int k = arr[i];
|
||||||
|
arr[i] = arr[(i * 15) % 100];
|
||||||
|
arr[(i * 15) % 100] = k;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
assert(sc_heap_add(&heap, i, (void *) (uintptr_t) i) == true);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
assert(sc_heap_pop(&heap, &key, &data) == true);
|
||||||
|
assert(key == i);
|
||||||
|
assert(data == (void *) (uintptr_t) i);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(sc_heap_peek(&heap, &key, &data) == false);
|
||||||
|
assert(sc_heap_pop(&heap, &key, &data) == false);
|
||||||
|
|
||||||
|
assert(sc_heap_size(&heap) == 0);
|
||||||
|
assert(sc_heap_add(&heap, 1, NULL) == true);
|
||||||
|
assert(sc_heap_size(&heap) == 1);
|
||||||
|
sc_heap_clear(&heap);
|
||||||
|
assert(sc_heap_size(&heap) == 0);
|
||||||
|
assert(sc_heap_add(&heap, 1, NULL) == true);
|
||||||
|
assert(sc_heap_size(&heap) == 1);
|
||||||
|
|
||||||
|
sc_heap_term(&heap);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef SC_HAVE_WRAP
|
||||||
|
|
||||||
|
bool fail_malloc = false;
|
||||||
|
void *__real_malloc(size_t n);
|
||||||
|
void *__wrap_malloc(size_t n)
|
||||||
|
{
|
||||||
|
if (fail_malloc) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return __real_malloc(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fail_realloc = false;
|
||||||
|
void *__real_realloc(void *p, size_t size);
|
||||||
|
void *__wrap_realloc(void *p, size_t n)
|
||||||
|
{
|
||||||
|
if (fail_realloc) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return __real_realloc(p, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
void fail_test(void)
|
||||||
|
{
|
||||||
|
struct sc_heap heap;
|
||||||
|
|
||||||
|
assert(sc_heap_init(&heap, 2) == true);
|
||||||
|
|
||||||
|
size_t count = SC_SIZE_MAX / sizeof(struct sc_heap_data);
|
||||||
|
bool success = false;
|
||||||
|
|
||||||
|
for (int i = 0; i < count + 5; i++) {
|
||||||
|
success = sc_heap_add(&heap, i, (void *) (uintptr_t) i);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(!success);
|
||||||
|
|
||||||
|
sc_heap_term(&heap);
|
||||||
|
|
||||||
|
assert(sc_heap_init(&heap, 0) == true);
|
||||||
|
|
||||||
|
fail_realloc = true;
|
||||||
|
assert(sc_heap_add(&heap, 1, NULL) == false);
|
||||||
|
fail_realloc = false;
|
||||||
|
|
||||||
|
sc_heap_term(&heap);
|
||||||
|
|
||||||
|
fail_malloc = true;
|
||||||
|
assert(sc_heap_init(&heap, 1) == false);
|
||||||
|
|
||||||
|
fail_malloc = false;
|
||||||
|
assert(sc_heap_init(&heap, 1) == true);
|
||||||
|
|
||||||
|
sc_heap_term(&heap);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
void fail_test()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
example();
|
||||||
|
fail_test();
|
||||||
|
test1();
|
||||||
|
test2();
|
||||||
|
test3();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
148
heap/sc_heap.c
Normal file
148
heap/sc_heap.c
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020 Ozan Tezcan
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "sc_heap.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#ifndef SC_SIZE_MAX
|
||||||
|
#define SC_SIZE_MAX SIZE_MAX
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define SC_CAP_MAX SC_SIZE_MAX / sizeof(struct sc_heap_data)
|
||||||
|
|
||||||
|
bool sc_heap_init(struct sc_heap *heap, size_t cap)
|
||||||
|
{
|
||||||
|
void *elems;
|
||||||
|
const size_t alloc = cap * sizeof(struct sc_heap_data);
|
||||||
|
|
||||||
|
*heap = (struct sc_heap){0};
|
||||||
|
|
||||||
|
if (cap == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check overflow
|
||||||
|
if (cap > SC_CAP_MAX || (elems = sc_heap_malloc(alloc)) == NULL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
heap->elems = elems;
|
||||||
|
heap->cap = cap;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sc_heap_term(struct sc_heap *heap)
|
||||||
|
{
|
||||||
|
sc_heap_free(heap->elems);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t sc_heap_size(struct sc_heap *heap)
|
||||||
|
{
|
||||||
|
return heap->size;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sc_heap_clear(struct sc_heap *heap)
|
||||||
|
{
|
||||||
|
heap->size = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sc_heap_add(struct sc_heap *heap, int64_t key, void *data)
|
||||||
|
{
|
||||||
|
size_t i;
|
||||||
|
void *exp;
|
||||||
|
|
||||||
|
if (++heap->size >= heap->cap) {
|
||||||
|
const size_t cap = heap->cap != 0 ? heap->cap * 2 : 4;
|
||||||
|
const size_t alloc = cap * 2 * sizeof(struct sc_heap_data);
|
||||||
|
// Check overflow
|
||||||
|
if (heap->cap >= SC_CAP_MAX / 2 ||
|
||||||
|
(exp = sc_heap_realloc(heap->elems, alloc)) == NULL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
heap->elems = exp;
|
||||||
|
heap->cap = cap;
|
||||||
|
}
|
||||||
|
|
||||||
|
i = heap->size;
|
||||||
|
while (i != 1 && key < heap->elems[i / 2].key) {
|
||||||
|
heap->elems[i] = heap->elems[i / 2];
|
||||||
|
i /= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
heap->elems[i].key = key;
|
||||||
|
heap->elems[i].data = data;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sc_heap_peek(struct sc_heap *heap, int64_t *key, void **data)
|
||||||
|
{
|
||||||
|
if (heap->size == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Top element is always at heap->elems[1].
|
||||||
|
*key = heap->elems[1].key;
|
||||||
|
*data = heap->elems[1].data;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sc_heap_pop(struct sc_heap *heap, int64_t *key, void **data)
|
||||||
|
{
|
||||||
|
size_t i = 1, child = 2;
|
||||||
|
struct sc_heap_data last;
|
||||||
|
|
||||||
|
if (heap->size == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Top element is always at heap->elems[1].
|
||||||
|
*key = heap->elems[1].key;
|
||||||
|
*data = heap->elems[1].data;
|
||||||
|
|
||||||
|
last = heap->elems[heap->size--];
|
||||||
|
while (child <= heap->size) {
|
||||||
|
if (child < heap->size &&
|
||||||
|
heap->elems[child].key > heap->elems[child + 1].key) {
|
||||||
|
child++;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (last.key <= heap->elems[child].key) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
heap->elems[i] = heap->elems[child];
|
||||||
|
|
||||||
|
i = child;
|
||||||
|
child *= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
heap->elems[i] = last;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
108
heap/sc_heap.h
Normal file
108
heap/sc_heap.h
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020 Ozan Tezcan
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef SC_HEAP_H
|
||||||
|
#define SC_HEAP_H
|
||||||
|
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
|
||||||
|
struct sc_heap_data
|
||||||
|
{
|
||||||
|
int64_t key;
|
||||||
|
void *data;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct sc_heap
|
||||||
|
{
|
||||||
|
size_t cap;
|
||||||
|
size_t size;
|
||||||
|
struct sc_heap_data *elems;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plug your memory allocator.
|
||||||
|
*/
|
||||||
|
#define sc_heap_malloc malloc
|
||||||
|
#define sc_heap_realloc realloc
|
||||||
|
#define sc_heap_free free
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param heap Heap
|
||||||
|
* @param cap Initial capacity, pass '0' for no initial memory allocation
|
||||||
|
* @return 'true' on success, 'false' on failure (memory allocation failure)
|
||||||
|
*/
|
||||||
|
bool sc_heap_init(struct sc_heap *heap, size_t cap);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroys heap, frees memory
|
||||||
|
* @param heap Heap
|
||||||
|
*/
|
||||||
|
void sc_heap_term(struct sc_heap *heap);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param heap Heap
|
||||||
|
* @return Current element count
|
||||||
|
*/
|
||||||
|
size_t sc_heap_size(struct sc_heap *heap);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears elements from the queue, does not free the allocated memory.
|
||||||
|
* @param heap heap pointer
|
||||||
|
*/
|
||||||
|
void sc_heap_clear(struct sc_heap *heap);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param heap Heap
|
||||||
|
* @param key Key
|
||||||
|
* @param data Data
|
||||||
|
* @return 'false' on out of memory.
|
||||||
|
*/
|
||||||
|
bool sc_heap_add(struct sc_heap *heap, int64_t key, void *data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read top element without removing from the heap.
|
||||||
|
*
|
||||||
|
* @param heap Heap
|
||||||
|
* @param key [out] key
|
||||||
|
* @param data [out] data
|
||||||
|
* @return 'false' if there is no element in the heap.
|
||||||
|
*/
|
||||||
|
bool sc_heap_peek(struct sc_heap *heap, int64_t *key, void **data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read top element and remove it from the heap.
|
||||||
|
*
|
||||||
|
* @param heap Heap
|
||||||
|
* @param key [out] key
|
||||||
|
* @param data [out] data
|
||||||
|
* @return 'false' if there is no element in the heap.
|
||||||
|
*/
|
||||||
|
bool sc_heap_pop(struct sc_heap *heap, int64_t *key, void **data);
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
84
ini/CMakeLists.txt
Normal file
84
ini/CMakeLists.txt
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.5.1)
|
||||||
|
project(sc_ini C)
|
||||||
|
|
||||||
|
set(CMAKE_C_STANDARD 99)
|
||||||
|
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||||
|
set(CMAKE_C_EXTENSIONS OFF)
|
||||||
|
|
||||||
|
add_executable(sc_ini ini_example.c sc_ini.h sc_ini.c)
|
||||||
|
|
||||||
|
if (NOT CMAKE_C_COMPILER_ID MATCHES "MSVC")
|
||||||
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -Wall -pedantic -Werror")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# --------------------- Test Configuration Start ---------------------------- #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
|
||||||
|
include(CTest)
|
||||||
|
include(CheckCCompilerFlag)
|
||||||
|
|
||||||
|
enable_testing()
|
||||||
|
|
||||||
|
add_executable(${PROJECT_NAME}_test ini_test.c sc_ini.c)
|
||||||
|
|
||||||
|
if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||||
|
if ("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR
|
||||||
|
"${CMAKE_C_COMPILER_ID}" STREQUAL "GNU")
|
||||||
|
|
||||||
|
target_compile_options(${PROJECT_NAME}_test PRIVATE -fno-omit-frame-pointer)
|
||||||
|
|
||||||
|
if (SANITIZER)
|
||||||
|
target_compile_options(${PROJECT_NAME}_test PRIVATE -fsanitize=${SANITIZER})
|
||||||
|
target_link_options(${PROJECT_NAME}_test PRIVATE -fsanitize=${SANITIZER})
|
||||||
|
endif ()
|
||||||
|
endif ()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
add_test(NAME ${PROJECT_NAME}_test COMMAND ${PROJECT_NAME}_test)
|
||||||
|
|
||||||
|
SET(MEMORYCHECK_COMMAND_OPTIONS
|
||||||
|
"-q --log-fd=2 --trace-children=yes --track-origins=yes \
|
||||||
|
--leak-check=full --show-leak-kinds=all --show-reachable=yes \
|
||||||
|
--error-exitcode=255")
|
||||||
|
|
||||||
|
add_custom_target(valgrind_${PROJECT_NAME} ${CMAKE_COMMAND}
|
||||||
|
-E env CTEST_OUTPUT_ON_FAILURE=1
|
||||||
|
${CMAKE_CTEST_COMMAND} -C $<CONFIG>
|
||||||
|
--overwrite MemoryCheckCommandOptions=${MEMORYCHECK_COMMAND_OPTIONS}
|
||||||
|
--verbose -T memcheck WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||||
|
|
||||||
|
add_custom_target(check_${PROJECT_NAME} ${CMAKE_COMMAND}
|
||||||
|
-E env CTEST_OUTPUT_ON_FAILURE=1
|
||||||
|
${CMAKE_CTEST_COMMAND} -C $<CONFIG> --verbose
|
||||||
|
WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||||
|
|
||||||
|
# ----------------------- - Code Coverage Start ----------------------------- #
|
||||||
|
|
||||||
|
if (${CMAKE_BUILD_TYPE} MATCHES "Coverage")
|
||||||
|
if ("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU")
|
||||||
|
target_compile_options(${PROJECT_NAME}_test PRIVATE --coverage)
|
||||||
|
target_link_libraries(${PROJECT_NAME}_test gcov)
|
||||||
|
else()
|
||||||
|
message(FATAL_ERROR "Only GCC is supported for coverage")
|
||||||
|
endif()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
add_custom_target(coverage_${PROJECT_NAME})
|
||||||
|
add_custom_command(
|
||||||
|
TARGET coverage_${PROJECT_NAME}
|
||||||
|
COMMAND lcov --capture --directory ..
|
||||||
|
--output-file coverage.info --rc lcov_branch_coverage=1
|
||||||
|
COMMAND lcov --remove coverage.info '/usr/*' '*example*' '*test*'
|
||||||
|
--output-file coverage.info --rc lcov_branch_coverage=1
|
||||||
|
COMMAND lcov --ini coverage.info --rc lcov_branch_coverage=1
|
||||||
|
)
|
||||||
|
|
||||||
|
add_dependencies(coverage_${PROJECT_NAME} check_${PROJECT_NAME})
|
||||||
|
|
||||||
|
# -------------------------- Code Coverage End ------------------------------ #
|
||||||
|
|
||||||
|
|
||||||
|
# ----------------------- Test Configuration End ---------------------------- #
|
||||||
|
|
116
ini/README.md
Normal file
116
ini/README.md
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
# INI parser
|
||||||
|
|
||||||
|
### Overview
|
||||||
|
|
||||||
|
- Simple ini file parser
|
||||||
|
- Just copy <b>sc_ini.h</b> and <b>sc_ini.c</b> to your project.
|
||||||
|
|
||||||
|
#### Memory
|
||||||
|
|
||||||
|
- No heap memory allocation internally.
|
||||||
|
|
||||||
|
#### Features
|
||||||
|
|
||||||
|
##### Comment example
|
||||||
|
```ini
|
||||||
|
#Comment
|
||||||
|
;Another comment
|
||||||
|
|
||||||
|
[Network]
|
||||||
|
#This is comment
|
||||||
|
hostname = github.com #Line comments start with space. Either " ;" or " #"
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
##### No section
|
||||||
|
Possible to use without sections
|
||||||
|
|
||||||
|
```ini
|
||||||
|
key1 = value1 ;Comment x
|
||||||
|
key2 = value2 ;Comment y
|
||||||
|
key3 = value3 #Comment z
|
||||||
|
```
|
||||||
|
```
|
||||||
|
- Item 1 : ""(Section), "key1"(Key), "value1"(Value)
|
||||||
|
- Item 2 : ""(Section), "key2"(Key), "value2"(Value)
|
||||||
|
- Item 3 : ""(Section), "key3"(Key), "value3"(Value)
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Multi-value
|
||||||
|
Values without keys in the next line will be reported as if belongs to previous
|
||||||
|
key. Those values should be indented at least with a single space character.
|
||||||
|
|
||||||
|
```ini
|
||||||
|
#Comment
|
||||||
|
;Another comment
|
||||||
|
|
||||||
|
[Network]
|
||||||
|
#This is comment
|
||||||
|
hostname = github.com
|
||||||
|
github.io
|
||||||
|
github.org
|
||||||
|
```
|
||||||
|
```
|
||||||
|
- Item 1 : "Network"(Section), "hostname"(Key), "github.com"(Value)
|
||||||
|
- Item 2 : "Network"(Section), "hostname"(Key), "github.io"(Value)
|
||||||
|
- Item 3 : "Network"(Section), "hostname"(Key), "github.org"(Value)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Usage
|
||||||
|
|
||||||
|
|
||||||
|
```c
|
||||||
|
|
||||||
|
#include "sc_ini.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
const char *example_ini = "# My configuration"
|
||||||
|
"[Network] \n"
|
||||||
|
"hostname = github.com \n"
|
||||||
|
"port = 443 \n"
|
||||||
|
"protocol = https \n"
|
||||||
|
"repo = any";
|
||||||
|
|
||||||
|
int callback(int line, void *arg, const char *section, const char *key,
|
||||||
|
const char *value)
|
||||||
|
{
|
||||||
|
printf("Line : %d, Section : %s, Key : %s, Value : %s \n", line, section,
|
||||||
|
key, value);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void file_example(void)
|
||||||
|
{
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
FILE *fp = fopen("my_config.ini", "w+");
|
||||||
|
fwrite(example_ini, 1, strlen(example_ini), fp);
|
||||||
|
fclose(fp);
|
||||||
|
|
||||||
|
printf(" \n Parse file \n");
|
||||||
|
|
||||||
|
rc = sc_ini_parse_file(NULL, callback, "my_config.ini");
|
||||||
|
assert(rc == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void string_example(void)
|
||||||
|
{
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
printf(" \n Parse string \n");
|
||||||
|
|
||||||
|
rc = sc_ini_parse_string(NULL, callback, example_ini);
|
||||||
|
assert(rc == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
string_example();
|
||||||
|
file_example();
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
51
ini/ini_example.c
Normal file
51
ini/ini_example.c
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
#include "sc_ini.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
const char *example_ini = "# My configuration"
|
||||||
|
"[Network] \n"
|
||||||
|
"hostname = github.com \n"
|
||||||
|
"port = 443 \n"
|
||||||
|
"protocol = https \n"
|
||||||
|
"repo = any";
|
||||||
|
|
||||||
|
int callback(int line, void *arg, const char *section, const char *key,
|
||||||
|
const char *value)
|
||||||
|
{
|
||||||
|
printf("Line : %d, Section : %s, Key : %s, Value : %s \n", line, section,
|
||||||
|
key, value);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void file_example(void)
|
||||||
|
{
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
FILE *fp = fopen("my_config.ini", "w+");
|
||||||
|
fwrite(example_ini, 1, strlen(example_ini), fp);
|
||||||
|
fclose(fp);
|
||||||
|
|
||||||
|
printf(" \n Parse file \n");
|
||||||
|
|
||||||
|
rc = sc_ini_parse_file(NULL, callback, "my_config.ini");
|
||||||
|
assert(rc == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void string_example(void)
|
||||||
|
{
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
printf(" \n Parse string \n");
|
||||||
|
|
||||||
|
rc = sc_ini_parse_string(NULL, callback, example_ini);
|
||||||
|
assert(rc == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
string_example();
|
||||||
|
file_example();
|
||||||
|
}
|
474
ini/ini_test.c
Normal file
474
ini/ini_test.c
Normal file
@ -0,0 +1,474 @@
|
|||||||
|
#include "sc_ini.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <memory.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
int cb(int line, void *arg, const char *section, const char *key,
|
||||||
|
const char *value)
|
||||||
|
{
|
||||||
|
printf("%s %s %s \n", section, key, value);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void test1()
|
||||||
|
{
|
||||||
|
int rc;
|
||||||
|
static const char *ini = "#Sample \n"
|
||||||
|
"[section \n";
|
||||||
|
|
||||||
|
rc = sc_ini_parse_string(NULL, cb, ini);
|
||||||
|
assert(rc == 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
int cb2(int line, void *arg, const char *section, const char *key,
|
||||||
|
const char *value)
|
||||||
|
{
|
||||||
|
assert(strcmp(section, "section") == 0);
|
||||||
|
assert(strcmp(key, "key") == 0);
|
||||||
|
assert(strcmp(value, "value") == 0);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void test2()
|
||||||
|
{
|
||||||
|
int rc;
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
static const char *ini = "#Sample \n"
|
||||||
|
"[section] \n"
|
||||||
|
"key = value \n"
|
||||||
|
"key : value ";
|
||||||
|
|
||||||
|
rc = sc_ini_parse_string(&count, cb2, ini);
|
||||||
|
assert(rc == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int cb3(int line, void *arg, const char *section, const char *key,
|
||||||
|
const char *value)
|
||||||
|
{
|
||||||
|
assert(strcmp(section, "") == 0);
|
||||||
|
assert(strcmp(key, "key") == 0);
|
||||||
|
assert(strcmp(value, "value") == 0);
|
||||||
|
|
||||||
|
*(int *) arg = *(int *) arg + 1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void test3()
|
||||||
|
{
|
||||||
|
int rc;
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
static const char *ini = " ;Sample \n"
|
||||||
|
"key = value \n"
|
||||||
|
"key : value ";
|
||||||
|
|
||||||
|
rc = sc_ini_parse_string(&count, cb3, ini);
|
||||||
|
assert(rc == 0);
|
||||||
|
assert(count == 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
int cb4(int line, void *arg, const char *section, const char *key,
|
||||||
|
const char *value)
|
||||||
|
{
|
||||||
|
char tmp[16];
|
||||||
|
int count = *(int *) arg;
|
||||||
|
|
||||||
|
assert(strcmp(section, "section") == 0);
|
||||||
|
assert(strcmp(key, "key") == 0);
|
||||||
|
|
||||||
|
snprintf(tmp, 16, "value%d", count);
|
||||||
|
assert(strcmp(value, tmp) == 0);
|
||||||
|
|
||||||
|
*(int *) arg = count + 1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void test4()
|
||||||
|
{
|
||||||
|
int rc;
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
static const char *ini = " ;Sample \n"
|
||||||
|
" [section] \n"
|
||||||
|
"key = value0 \n"
|
||||||
|
" value1 \n"
|
||||||
|
" value2 ";
|
||||||
|
|
||||||
|
rc = sc_ini_parse_string(&count, cb4, ini);
|
||||||
|
assert(rc == 0);
|
||||||
|
assert(count == 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
int cb5(int line, void *arg, const char *section, const char *key,
|
||||||
|
const char *value)
|
||||||
|
{
|
||||||
|
char tmp[16];
|
||||||
|
int count = *(int *) arg;
|
||||||
|
|
||||||
|
assert(strcmp(section, "section") == 0);
|
||||||
|
assert(strcmp(key, "key") == 0);
|
||||||
|
|
||||||
|
snprintf(tmp, 16, "value%d", count);
|
||||||
|
assert(strcmp(value, tmp) == 0);
|
||||||
|
|
||||||
|
*(int *) arg = count + 1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void test5()
|
||||||
|
{
|
||||||
|
int rc;
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
static const char *ini = " ;Sample \n"
|
||||||
|
" [section] \n"
|
||||||
|
"key = value0 \n"
|
||||||
|
"value1 \n"
|
||||||
|
"value2 ";
|
||||||
|
|
||||||
|
rc = sc_ini_parse_string(&count, cb4, ini);
|
||||||
|
assert(rc == 4);
|
||||||
|
assert(count == 1);
|
||||||
|
|
||||||
|
|
||||||
|
count = 0;
|
||||||
|
static const char *ini2 = " ;Sample \n"
|
||||||
|
" [section] \n"
|
||||||
|
"key = value0 \n"
|
||||||
|
" value1 \n"
|
||||||
|
"value2 ";
|
||||||
|
rc = sc_ini_parse_string(&count, cb4, ini2);
|
||||||
|
assert(rc == 5);
|
||||||
|
assert(count == 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
int cb6(int line, void *arg, const char *section, const char *key,
|
||||||
|
const char *value)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void test6()
|
||||||
|
{
|
||||||
|
int rc;
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
static const char *ini = " ;Sample \n"
|
||||||
|
" [section] \n"
|
||||||
|
"key = value0 \n"
|
||||||
|
" value1 \n"
|
||||||
|
" value2 ";
|
||||||
|
|
||||||
|
rc = sc_ini_parse_string(&count, cb6, ini);
|
||||||
|
assert(rc == 3);
|
||||||
|
assert(count == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int cb7(int line, void *arg, const char *section, const char *key,
|
||||||
|
const char *value)
|
||||||
|
{
|
||||||
|
char tmp[16];
|
||||||
|
int count = *(int *) arg;
|
||||||
|
if (count == 1) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(strcmp(section, "section") == 0);
|
||||||
|
assert(strcmp(key, "key") == 0);
|
||||||
|
|
||||||
|
snprintf(tmp, 16, "value%d", count);
|
||||||
|
assert(strcmp(value, tmp) == 0);
|
||||||
|
|
||||||
|
*(int *) arg = count + 1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void test7()
|
||||||
|
{
|
||||||
|
int rc;
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
static const char *ini = " ;Sample \n"
|
||||||
|
" [section] \n"
|
||||||
|
"key = value0 \n"
|
||||||
|
" value1 \n"
|
||||||
|
" value2 ";
|
||||||
|
|
||||||
|
rc = sc_ini_parse_string(&count, cb7, ini);
|
||||||
|
assert(rc == 4);
|
||||||
|
assert(count == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int cb8(int line, void *arg, const char *section, const char *key,
|
||||||
|
const char *value)
|
||||||
|
{
|
||||||
|
char tmp[16];
|
||||||
|
int count = *(int *) arg;
|
||||||
|
|
||||||
|
assert(strcmp(section, "section") == 0);
|
||||||
|
assert(strcmp(key, "key") == 0);
|
||||||
|
|
||||||
|
snprintf(tmp, 16, "value%d", count);
|
||||||
|
assert(strcmp(value, tmp) == 0);
|
||||||
|
|
||||||
|
*(int *) arg = count + 1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void test8()
|
||||||
|
{
|
||||||
|
int rc;
|
||||||
|
int count = 0;
|
||||||
|
FILE *fp;
|
||||||
|
|
||||||
|
static const char *ini = " ;Sample \n"
|
||||||
|
" [section] \n"
|
||||||
|
"key = value0 #;comment\n"
|
||||||
|
" value1 \n"
|
||||||
|
" value2 ";
|
||||||
|
|
||||||
|
fp = fopen("config.ini", "w+");
|
||||||
|
fwrite(ini, 1, strlen(ini), fp);
|
||||||
|
fclose(fp);
|
||||||
|
|
||||||
|
rc = sc_ini_parse_file(&count, cb8, "config.ini");
|
||||||
|
assert(rc == 0);
|
||||||
|
assert(count == 3);
|
||||||
|
remove("config.ini");
|
||||||
|
|
||||||
|
count = 0;
|
||||||
|
static const char *ini2 = " ;Sample \n"
|
||||||
|
" [section] \n"
|
||||||
|
"key = value0 ;comment\n"
|
||||||
|
" value1 #comment\n"
|
||||||
|
" value2 ;#comment\n";
|
||||||
|
|
||||||
|
fp = fopen("config.ini", "w+");
|
||||||
|
fwrite(ini2, 1, strlen(ini2), fp);
|
||||||
|
fclose(fp);
|
||||||
|
|
||||||
|
rc = sc_ini_parse_file(&count, cb8, "config.ini");
|
||||||
|
assert(rc == 0);
|
||||||
|
assert(count == 3);
|
||||||
|
remove("config.ini");
|
||||||
|
|
||||||
|
|
||||||
|
count = 0;
|
||||||
|
unsigned char bom[3] = {0xEF, 0xBB, 0xBF};
|
||||||
|
static const char *ini3 = " ;Sample \n"
|
||||||
|
" [section] \n"
|
||||||
|
"key = value0 \n"
|
||||||
|
" value1 \n"
|
||||||
|
" value2 \n";
|
||||||
|
|
||||||
|
fp = fopen("config.ini", "w+");
|
||||||
|
fwrite(bom, 1, sizeof(bom), fp);
|
||||||
|
fwrite(ini3, 1, strlen(ini3), fp);
|
||||||
|
fclose(fp);
|
||||||
|
|
||||||
|
rc = sc_ini_parse_file(&count, cb8, "config.ini");
|
||||||
|
assert(rc == 0);
|
||||||
|
assert(count == 3);
|
||||||
|
remove("config.ini");
|
||||||
|
|
||||||
|
rc = sc_ini_parse_file(&count, cb8, "config.ini");
|
||||||
|
assert(rc == -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int cb9(int line, void *arg, const char *section, const char *key,
|
||||||
|
const char *value)
|
||||||
|
{
|
||||||
|
assert(strcmp(section, "section[test") == 0);
|
||||||
|
assert(strcmp(key, "key;;") == 0);
|
||||||
|
assert(strcmp(value, "value;;") == 0);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void test9()
|
||||||
|
{
|
||||||
|
int rc;
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
static const char *ini = "#Sample \n"
|
||||||
|
"[section[test]] \n"
|
||||||
|
"key;; = value;; ;comment \n"
|
||||||
|
"key;; : value;; #comment ";
|
||||||
|
|
||||||
|
rc = sc_ini_parse_string(&count, cb9, ini);
|
||||||
|
assert(rc == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test10()
|
||||||
|
{
|
||||||
|
int rc;
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
static const char *ini = "#Sample \n"
|
||||||
|
"section[test]] \n"
|
||||||
|
"key = value;; ;comment \n"
|
||||||
|
"key : value# #comment ";
|
||||||
|
|
||||||
|
rc = sc_ini_parse_string(&count, cb9, ini);
|
||||||
|
assert(rc == 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
int cb11(int line, void *arg, const char *section, const char *key,
|
||||||
|
const char *value)
|
||||||
|
{
|
||||||
|
assert(strcmp(section, "section") == 0);
|
||||||
|
assert(strcmp(key, "key#") == 0);
|
||||||
|
assert(strcmp(value, "") == 0);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void test11()
|
||||||
|
{
|
||||||
|
int rc;
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
static const char *ini = "#Sample \n"
|
||||||
|
"[section] \n"
|
||||||
|
"#comment1 \n"
|
||||||
|
";comment2 \n"
|
||||||
|
" #comment3 \n"
|
||||||
|
" ;comment44 \n"
|
||||||
|
"key# = ;comment \n"
|
||||||
|
"key# : #comment ";
|
||||||
|
|
||||||
|
rc = sc_ini_parse_string(&count, cb11, ini);
|
||||||
|
assert(rc == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int cb12(int line, void *arg, const char *section, const char *key,
|
||||||
|
const char *value)
|
||||||
|
{
|
||||||
|
assert(strcmp(section, "") == 0);
|
||||||
|
assert(strcmp(key, "") == 0);
|
||||||
|
assert(strcmp(value, "") == 0);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void test12()
|
||||||
|
{
|
||||||
|
int rc;
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
static const char *ini = "#Sample \n"
|
||||||
|
"#[section] \n"
|
||||||
|
"#comment1 \n"
|
||||||
|
";comment2 \n"
|
||||||
|
" #comment3 \n"
|
||||||
|
" ;comment44 \n"
|
||||||
|
" = ;comment \n"
|
||||||
|
" : #comment ";
|
||||||
|
|
||||||
|
rc = sc_ini_parse_string(&count, cb12, ini);
|
||||||
|
assert(rc == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int cb13(int line, void *arg, const char *section, const char *key,
|
||||||
|
const char *value)
|
||||||
|
{
|
||||||
|
char tmp[16];
|
||||||
|
int count = *(int *) arg;
|
||||||
|
|
||||||
|
assert(strcmp(section, "section") == 0);
|
||||||
|
assert(strcmp(key, "key") == 0);
|
||||||
|
|
||||||
|
snprintf(tmp, 16, "value%d", count);
|
||||||
|
assert(strcmp(value, tmp) == 0);
|
||||||
|
|
||||||
|
*(int *) arg = count + 1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void test13()
|
||||||
|
{
|
||||||
|
int rc;
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
static const char *ini = " ;Sample \n"
|
||||||
|
" [section] \n"
|
||||||
|
"key = value0 \n"
|
||||||
|
" \n"
|
||||||
|
" value1 ";
|
||||||
|
|
||||||
|
rc = sc_ini_parse_string(&count, cb13, ini);
|
||||||
|
assert(rc == 0);
|
||||||
|
assert(count == 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *example_ini = "# My configuration"
|
||||||
|
"[Network] \n"
|
||||||
|
"hostname = github.com \n"
|
||||||
|
"port = 443 \n"
|
||||||
|
"protocol = https \n"
|
||||||
|
"repo = any";
|
||||||
|
|
||||||
|
int callback(int line, void *arg, const char *section, const char *key,
|
||||||
|
const char *value)
|
||||||
|
{
|
||||||
|
printf("Line : %d, Section : %s, Key : %s, Value : %s \n", line, section,
|
||||||
|
key, value);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void file_example(void)
|
||||||
|
{
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
FILE *fp = fopen("my_config.ini", "w+");
|
||||||
|
fwrite(example_ini, 1, strlen(example_ini), fp);
|
||||||
|
fclose(fp);
|
||||||
|
|
||||||
|
printf(" \n Parse file \n");
|
||||||
|
|
||||||
|
rc = sc_ini_parse_file(NULL, callback, "my_config.ini");
|
||||||
|
assert(rc == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void string_example(void)
|
||||||
|
{
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
printf(" \n Parse string \n");
|
||||||
|
|
||||||
|
rc = sc_ini_parse_string(NULL, callback, example_ini);
|
||||||
|
assert(rc == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void example(void)
|
||||||
|
{
|
||||||
|
string_example();
|
||||||
|
file_example();
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
example();
|
||||||
|
test1();
|
||||||
|
test2();
|
||||||
|
test3();
|
||||||
|
test4();
|
||||||
|
test5();
|
||||||
|
test6();
|
||||||
|
test7();
|
||||||
|
test8();
|
||||||
|
test9();
|
||||||
|
test10();
|
||||||
|
test11();
|
||||||
|
test12();
|
||||||
|
test13();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
184
ini/sc_ini.c
Normal file
184
ini/sc_ini.c
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020 Ozan Tezcan
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "sc_ini.h"
|
||||||
|
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
static char *trim_space(char *str)
|
||||||
|
{
|
||||||
|
char *end;
|
||||||
|
|
||||||
|
while (isspace(*str)) {
|
||||||
|
str++;
|
||||||
|
}
|
||||||
|
|
||||||
|
end = str + strlen(str) - 1;
|
||||||
|
while (end > str && isspace(*end)) {
|
||||||
|
end--;
|
||||||
|
}
|
||||||
|
|
||||||
|
end[1] = '\0';
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *trim_comment(char *str)
|
||||||
|
{
|
||||||
|
char *s = str;
|
||||||
|
|
||||||
|
if (*s == '\0' || *s == ';' || *s == '#') {
|
||||||
|
*s = '\0';
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (*s && (s = strchr(s, ' ')) != NULL) {
|
||||||
|
s++;
|
||||||
|
if (*s == ';' || *s == '#') {
|
||||||
|
*s = '\0';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *trim_bom(char *str)
|
||||||
|
{
|
||||||
|
if (str != NULL && strlen(str)) {
|
||||||
|
if ((uint8_t) str[0] == 0xEF && (uint8_t) str[1] == 0xBB &&
|
||||||
|
(uint8_t) str[2] == 0xBF) {
|
||||||
|
str += 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int sc_ini_parse(void *arg, sc_ini_on_item on_item, void *arg1,
|
||||||
|
char *(*next_line)(void *arg, char *buf, size_t size))
|
||||||
|
{
|
||||||
|
int rc = 0, line = 0;
|
||||||
|
char buf[SC_INI_MAX_LINE_LEN];
|
||||||
|
char section[256] = {0}, prev_key[256] = {0};
|
||||||
|
char *head, *end;
|
||||||
|
|
||||||
|
while ((head = next_line(arg1, buf, sizeof(buf) - 1)) != NULL) {
|
||||||
|
if (++line == 1) {
|
||||||
|
// Skip byte order mark
|
||||||
|
head = trim_bom(head);
|
||||||
|
}
|
||||||
|
|
||||||
|
head = trim_space(trim_comment(head));
|
||||||
|
if (*head == '\0') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (head > buf && *prev_key) {
|
||||||
|
// Multi-line case. This line is another value to previous key.
|
||||||
|
rc = on_item(line, arg, section, prev_key, head);
|
||||||
|
} else if (*head == '[') {
|
||||||
|
if ((end = strchr(head, ']')) == NULL) {
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
|
||||||
|
*prev_key = '\0';
|
||||||
|
*end = '\0';
|
||||||
|
strncpy(section, head + 1, sizeof(section) - 1);
|
||||||
|
} else {
|
||||||
|
if ((end = strpbrk(head, "=:")) == NULL) {
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
|
||||||
|
*end = '\0';
|
||||||
|
trim_space(head);
|
||||||
|
strncpy(prev_key, head, sizeof(prev_key) - 1);
|
||||||
|
rc = on_item(line, arg, section, head, trim_space(end + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rc != 0) {
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *file_next_line(void *p, char *buf, size_t size)
|
||||||
|
{
|
||||||
|
return fgets(buf, size, (FILE *) p);
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *string_next_line(void *p, char *buf, size_t size)
|
||||||
|
{
|
||||||
|
size_t len;
|
||||||
|
char *t;
|
||||||
|
char *str = (*(char **) p);
|
||||||
|
|
||||||
|
if (str == NULL || *str == '\0') {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
t = strchr(str, '\n');
|
||||||
|
if (t == NULL) {
|
||||||
|
t = str + strlen(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
len = (t - str) < size ? (t - str) : size;
|
||||||
|
memcpy(buf, str, len);
|
||||||
|
buf[len] = '\0';
|
||||||
|
|
||||||
|
*(char **) p = *t == '\0' ? '\0' : t + 1;
|
||||||
|
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
int sc_ini_parse_file(void *arg, sc_ini_on_item on_item, const char *filename)
|
||||||
|
{
|
||||||
|
int rc;
|
||||||
|
FILE *file;
|
||||||
|
|
||||||
|
file = fopen(filename, "rb");
|
||||||
|
if (!file) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
rc = sc_ini_parse(arg, on_item, file, file_next_line);
|
||||||
|
if (rc == 0) {
|
||||||
|
rc = ferror(file) != 0 ? -1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(file);
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
int sc_ini_parse_string(void *arg, sc_ini_on_item on_item, const char *str)
|
||||||
|
{
|
||||||
|
char *ptr = (char *) str;
|
||||||
|
|
||||||
|
return sc_ini_parse(arg, on_item, &ptr, string_next_line);
|
||||||
|
}
|
71
ini/sc_ini.h
Normal file
71
ini/sc_ini.h
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020 Ozan Tezcan
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef SC_INI_H
|
||||||
|
#define SC_INI_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adjust max line length here. If a line is larger than this, it will be
|
||||||
|
* truncated silently.
|
||||||
|
*/
|
||||||
|
#define SC_INI_MAX_LINE_LEN 1024
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param line current line number
|
||||||
|
* @param arg user arg.
|
||||||
|
* @param section section.
|
||||||
|
* @param key key.
|
||||||
|
* @param value value.
|
||||||
|
* @return Return '0' on success, any other value will make parser
|
||||||
|
* stop and return error.
|
||||||
|
*/
|
||||||
|
typedef int (*sc_ini_on_item)(int line, void *arg, const char *section,
|
||||||
|
const char *key, const char *value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param arg User data to be passed to 'on_item' callback.
|
||||||
|
* @param on_item Callback.
|
||||||
|
* @param filename File name.
|
||||||
|
* @return - '0' on success,
|
||||||
|
* - '-1' on file IO error.
|
||||||
|
* - positive line number on parsing error
|
||||||
|
* - 'on_item' return value if it returns other than '0'
|
||||||
|
*/
|
||||||
|
int sc_ini_parse_file(void *arg, sc_ini_on_item on_item, const char *filename);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param arg User data to be passed to 'on_item' callback.
|
||||||
|
* @param on_item Callback
|
||||||
|
* @param str String to parse
|
||||||
|
* @return - '0' on success,
|
||||||
|
* - positive line number on parsing error
|
||||||
|
* - 'on_item' return value if it returns other than '0'
|
||||||
|
*/
|
||||||
|
int sc_ini_parse_string(void *arg, sc_ini_on_item on_item, const char *str);
|
||||||
|
|
||||||
|
#endif
|
83
linked-list/CMakeLists.txt
Normal file
83
linked-list/CMakeLists.txt
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.5.1)
|
||||||
|
project(sc_list C)
|
||||||
|
|
||||||
|
set(CMAKE_C_STANDARD 99)
|
||||||
|
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||||
|
set(CMAKE_C_EXTENSIONS OFF)
|
||||||
|
|
||||||
|
add_executable(sc_list list_example.c sc_list.h sc_list.c)
|
||||||
|
|
||||||
|
if (NOT CMAKE_C_COMPILER_ID MATCHES "MSVC")
|
||||||
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -Wall -pedantic -Werror")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# --------------------- Test Configuration Start ---------------------------- #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
|
||||||
|
include(CTest)
|
||||||
|
include(CheckCCompilerFlag)
|
||||||
|
|
||||||
|
enable_testing()
|
||||||
|
|
||||||
|
add_executable(${PROJECT_NAME}_test list_test.c sc_list.c)
|
||||||
|
|
||||||
|
if ("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR
|
||||||
|
"${CMAKE_C_COMPILER_ID}" STREQUAL "AppleClang" OR
|
||||||
|
"${CMAKE_C_COMPILER_ID}" STREQUAL "GNU")
|
||||||
|
|
||||||
|
target_compile_options(${PROJECT_NAME}_test PRIVATE -fno-omit-frame-pointer)
|
||||||
|
|
||||||
|
if (SANITIZER)
|
||||||
|
target_compile_options(${PROJECT_NAME}_test PRIVATE -fsanitize=${SANITIZER})
|
||||||
|
target_link_options(${PROJECT_NAME}_test PRIVATE -fsanitize=${SANITIZER})
|
||||||
|
endif ()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
add_test(NAME ${PROJECT_NAME}_test COMMAND ${PROJECT_NAME}_test)
|
||||||
|
|
||||||
|
SET(MEMORYCHECK_COMMAND_OPTIONS
|
||||||
|
"-q --log-fd=2 --trace-children=yes --track-origins=yes \
|
||||||
|
--leak-check=full --show-leak-kinds=all --show-reachable=yes \
|
||||||
|
--error-exitcode=255")
|
||||||
|
|
||||||
|
add_custom_target(valgrind_${PROJECT_NAME} ${CMAKE_COMMAND}
|
||||||
|
-E env CTEST_OUTPUT_ON_FAILURE=1
|
||||||
|
${CMAKE_CTEST_COMMAND} -C $<CONFIG>
|
||||||
|
--overwrite MemoryCheckCommandOptions=${MEMORYCHECK_COMMAND_OPTIONS}
|
||||||
|
--verbose -T memcheck WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||||
|
|
||||||
|
add_custom_target(check_${PROJECT_NAME} ${CMAKE_COMMAND}
|
||||||
|
-E env CTEST_OUTPUT_ON_FAILURE=1
|
||||||
|
${CMAKE_CTEST_COMMAND} -C $<CONFIG> --verbose
|
||||||
|
WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||||
|
|
||||||
|
# ----------------------- - Code Coverage Start ----------------------------- #
|
||||||
|
|
||||||
|
if (${CMAKE_BUILD_TYPE} MATCHES "Coverage")
|
||||||
|
if ("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU")
|
||||||
|
target_compile_options(${PROJECT_NAME}_test PRIVATE --coverage)
|
||||||
|
target_link_libraries(${PROJECT_NAME}_test gcov)
|
||||||
|
else()
|
||||||
|
message(FATAL_ERROR "Only GCC is supported for coverage")
|
||||||
|
endif()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
add_custom_target(coverage_${PROJECT_NAME})
|
||||||
|
add_custom_command(
|
||||||
|
TARGET coverage_${PROJECT_NAME}
|
||||||
|
COMMAND lcov --capture --directory ..
|
||||||
|
--output-file coverage.info --rc lcov_branch_coverage=1
|
||||||
|
COMMAND lcov --remove coverage.info '/usr/*' '*example*' '*test*'
|
||||||
|
--output-file coverage.info --rc lcov_branch_coverage=1
|
||||||
|
COMMAND lcov --list coverage.info --rc lcov_branch_coverage=1
|
||||||
|
)
|
||||||
|
|
||||||
|
add_dependencies(coverage_${PROJECT_NAME} check_${PROJECT_NAME})
|
||||||
|
|
||||||
|
# -------------------------- Code Coverage End ------------------------------ #
|
||||||
|
|
||||||
|
|
||||||
|
# ----------------------- Test Configuration End ---------------------------- #
|
||||||
|
|
66
linked-list/README.md
Normal file
66
linked-list/README.md
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
# Linked List
|
||||||
|
|
||||||
|
### Overview
|
||||||
|
|
||||||
|
- Intrusive doubly linked-list implementation.
|
||||||
|
- Basically, same as adding next and prev pointers to your structs.
|
||||||
|
- Add/remove from head/tail is possible so it can be used as list, stack,
|
||||||
|
queue, dequeue etc.
|
||||||
|
- Just copy <b><i>sc_list.h</i></b> and <b><i>sc_list.c</i></b> to your project.
|
||||||
|
|
||||||
|
|
||||||
|
#### Memory
|
||||||
|
|
||||||
|
- No heap memory allocation.
|
||||||
|
|
||||||
|
#### Performance
|
||||||
|
|
||||||
|
- Good fit if you already have structs allocated in memory and willing to put
|
||||||
|
them into a list without making extra allocations.
|
||||||
|
|
||||||
|
#### Usage
|
||||||
|
|
||||||
|
|
||||||
|
```c
|
||||||
|
|
||||||
|
#include "sc_list.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
struct user
|
||||||
|
{
|
||||||
|
char *name;
|
||||||
|
struct sc_list next;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct user users[] = {{"first"},
|
||||||
|
{"second"},
|
||||||
|
{"third"},
|
||||||
|
{"fourth"},
|
||||||
|
{"fifth"}};
|
||||||
|
|
||||||
|
struct sc_list list;
|
||||||
|
|
||||||
|
sc_list_init(&list);
|
||||||
|
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
sc_list_add_tail(&list, &users[i].next);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct sc_list *it;
|
||||||
|
struct user *user;
|
||||||
|
|
||||||
|
sc_list_foreach (&list, it) {
|
||||||
|
user = sc_list_entry(it, struct user, next);
|
||||||
|
printf("%s \n", user->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
```
|
38
linked-list/list_example.c
Normal file
38
linked-list/list_example.c
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
#include "sc_list.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
struct user
|
||||||
|
{
|
||||||
|
char *name;
|
||||||
|
struct sc_list next;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct user users[] = {{"first"},
|
||||||
|
{"second"},
|
||||||
|
{"third"},
|
||||||
|
{"fourth"},
|
||||||
|
{"fifth"}};
|
||||||
|
|
||||||
|
struct sc_list list;
|
||||||
|
|
||||||
|
sc_list_init(&list);
|
||||||
|
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
sc_list_add_tail(&list, &users[i].next);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct sc_list *it;
|
||||||
|
struct user *user;
|
||||||
|
|
||||||
|
sc_list_foreach (&list, it) {
|
||||||
|
user = sc_list_entry(it, struct user, next);
|
||||||
|
printf("%s \n", user->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
149
linked-list/list_test.c
Normal file
149
linked-list/list_test.c
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
#include "sc_list.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
struct elem
|
||||||
|
{
|
||||||
|
int id;
|
||||||
|
struct sc_list list;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void test1(void)
|
||||||
|
{
|
||||||
|
int k, i;
|
||||||
|
struct elem a, b, c, d, e, *elem;
|
||||||
|
struct sc_list list, *item, *tmp;
|
||||||
|
|
||||||
|
a.id = 1;
|
||||||
|
b.id = 2;
|
||||||
|
c.id = 3;
|
||||||
|
d.id = 4;
|
||||||
|
e.id = 5;
|
||||||
|
|
||||||
|
sc_list_init(&list);
|
||||||
|
|
||||||
|
assert(sc_list_count(&list) == 0);
|
||||||
|
tmp = sc_list_pop_head(&list);
|
||||||
|
assert(tmp == NULL);
|
||||||
|
|
||||||
|
tmp = sc_list_pop_tail(&list);
|
||||||
|
assert(tmp == NULL);
|
||||||
|
|
||||||
|
sc_list_add_tail(&list, &a.list);
|
||||||
|
sc_list_add_tail(&list, &b.list);
|
||||||
|
assert(sc_list_count(&list) == 2);
|
||||||
|
tmp = sc_list_pop_tail(&list);
|
||||||
|
elem = sc_list_entry(tmp, struct elem, list);
|
||||||
|
assert(elem->id == b.id);
|
||||||
|
|
||||||
|
sc_list_add_after(&list, &a.list, &b.list);
|
||||||
|
assert(a.list.next == &b.list);
|
||||||
|
|
||||||
|
sc_list_add_head(&list, &c.list);
|
||||||
|
tmp = sc_list_pop_head(&list);
|
||||||
|
elem = sc_list_entry(tmp, struct elem, list);
|
||||||
|
assert(elem->id == c.id);
|
||||||
|
|
||||||
|
sc_list_foreach (&list, item) {
|
||||||
|
elem = sc_list_entry(item, struct elem, list);
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_list_add_before(&list, &b.list, &e.list);
|
||||||
|
assert(a.list.next == &e.list);
|
||||||
|
|
||||||
|
sc_list_foreach (&list, item) {
|
||||||
|
elem = sc_list_entry(item, struct elem, list);
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_list_add_tail(&list, &c.list);
|
||||||
|
sc_list_add_tail(&list, &d.list);
|
||||||
|
|
||||||
|
sc_list_foreach (&list, item) {
|
||||||
|
elem = sc_list_entry(item, struct elem, list);
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_list_foreach (&list, item) {
|
||||||
|
elem = sc_list_entry(item, struct elem, list);
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_list_del(&list, &e.list);
|
||||||
|
|
||||||
|
sc_list_foreach (&list, item) {
|
||||||
|
elem = sc_list_entry(item, struct elem, list);
|
||||||
|
}
|
||||||
|
|
||||||
|
i = 1;
|
||||||
|
sc_list_foreach (&list, item) {
|
||||||
|
elem = sc_list_entry(item, struct elem, list);
|
||||||
|
assert(elem->id == i);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
i = 4;
|
||||||
|
sc_list_foreach_r(&list, item)
|
||||||
|
{
|
||||||
|
elem = sc_list_entry(item, struct elem, list);
|
||||||
|
assert(elem->id == i);
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_list_clear(&list);
|
||||||
|
|
||||||
|
assert(sc_list_is_empty(&list) == true);
|
||||||
|
assert(sc_list_count(&list) == 0);
|
||||||
|
assert(sc_list_head(&list) == NULL);
|
||||||
|
assert(sc_list_tail(&list) == NULL);
|
||||||
|
|
||||||
|
sc_list_add_tail(&list, &a.list);
|
||||||
|
sc_list_add_tail(&list, &b.list);
|
||||||
|
sc_list_add_tail(&list, &c.list);
|
||||||
|
sc_list_add_tail(&list, &d.list);
|
||||||
|
sc_list_add_tail(&list, &e.list);
|
||||||
|
|
||||||
|
assert(sc_list_head(&list) != NULL);
|
||||||
|
assert(sc_list_tail(&list) != NULL);
|
||||||
|
assert(sc_list_is_empty(&list) == false);
|
||||||
|
|
||||||
|
k = 0;
|
||||||
|
sc_list_foreach_safe (&list, tmp, item) {
|
||||||
|
if (k == 0) {
|
||||||
|
sc_list_del(&list, &e.list);
|
||||||
|
}
|
||||||
|
|
||||||
|
elem = sc_list_entry(item, struct elem, list);
|
||||||
|
|
||||||
|
k++;
|
||||||
|
assert(elem->id == k);
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_list_clear(&list);
|
||||||
|
|
||||||
|
|
||||||
|
sc_list_add_tail(&list, &a.list);
|
||||||
|
sc_list_add_tail(&list, &b.list);
|
||||||
|
sc_list_add_tail(&list, &c.list);
|
||||||
|
sc_list_add_tail(&list, &d.list);
|
||||||
|
sc_list_add_tail(&list, &e.list);
|
||||||
|
|
||||||
|
k = 6;
|
||||||
|
sc_list_foreach_safe_r(&list, tmp, item)
|
||||||
|
{
|
||||||
|
if (k == 3) {
|
||||||
|
sc_list_del(&list, &b.list);
|
||||||
|
k--;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
elem = sc_list_entry(item, struct elem, list);
|
||||||
|
|
||||||
|
k--;
|
||||||
|
assert(elem->id == k);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
test1();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
156
linked-list/sc_list.c
Normal file
156
linked-list/sc_list.c
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020 Ozan Tezcan
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "sc_list.h"
|
||||||
|
|
||||||
|
void sc_list_init(struct sc_list *list)
|
||||||
|
{
|
||||||
|
list->next = list;
|
||||||
|
list->prev = list;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sc_list_clear(struct sc_list *list)
|
||||||
|
{
|
||||||
|
list->next = list;
|
||||||
|
list->prev = list;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sc_list_is_empty(struct sc_list *list)
|
||||||
|
{
|
||||||
|
return list->next == list;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t sc_list_count(struct sc_list *list)
|
||||||
|
{
|
||||||
|
size_t count = 0;
|
||||||
|
struct sc_list *elem;
|
||||||
|
|
||||||
|
sc_list_foreach (list, elem) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct sc_list *sc_list_head(struct sc_list *list)
|
||||||
|
{
|
||||||
|
struct sc_list *elem;
|
||||||
|
|
||||||
|
elem = list->next;
|
||||||
|
if (elem == list) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return elem;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct sc_list *sc_list_tail(struct sc_list *list)
|
||||||
|
{
|
||||||
|
struct sc_list *elem;
|
||||||
|
|
||||||
|
elem = list->prev;
|
||||||
|
if (elem == list) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return elem;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sc_list_add_tail(struct sc_list *list, struct sc_list *elem)
|
||||||
|
{
|
||||||
|
struct sc_list *prev;
|
||||||
|
|
||||||
|
prev = list->prev;
|
||||||
|
list->prev = elem;
|
||||||
|
elem->next = list;
|
||||||
|
elem->prev = prev;
|
||||||
|
prev->next = elem;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct sc_list *sc_list_pop_tail(struct sc_list *list)
|
||||||
|
{
|
||||||
|
struct sc_list *tail;
|
||||||
|
|
||||||
|
if (sc_list_is_empty(list)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
tail = list->prev;
|
||||||
|
sc_list_del(list, list->prev);
|
||||||
|
|
||||||
|
return tail;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sc_list_add_head(struct sc_list *list, struct sc_list *elem)
|
||||||
|
{
|
||||||
|
sc_list_add_before(list, list->next, elem);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct sc_list *sc_list_pop_head(struct sc_list *list)
|
||||||
|
{
|
||||||
|
struct sc_list *head;
|
||||||
|
|
||||||
|
if (sc_list_is_empty(list)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
head = list->next;
|
||||||
|
sc_list_del(list, list->next);
|
||||||
|
|
||||||
|
return head;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sc_list_add_after(struct sc_list *list, struct sc_list *prev,
|
||||||
|
struct sc_list *elem)
|
||||||
|
{
|
||||||
|
(void) list;
|
||||||
|
struct sc_list *next;
|
||||||
|
|
||||||
|
next = prev->next;
|
||||||
|
prev->next = elem;
|
||||||
|
elem->next = next;
|
||||||
|
elem->prev = prev;
|
||||||
|
next->prev = elem;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sc_list_add_before(struct sc_list *list, struct sc_list *next,
|
||||||
|
struct sc_list *elem)
|
||||||
|
{
|
||||||
|
(void) list;
|
||||||
|
struct sc_list *prev;
|
||||||
|
|
||||||
|
prev = next->prev;
|
||||||
|
next->prev = elem;
|
||||||
|
elem->next = next;
|
||||||
|
elem->prev = prev;
|
||||||
|
prev->next = elem;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sc_list_del(struct sc_list *list, struct sc_list *elem)
|
||||||
|
{
|
||||||
|
(void) (list);
|
||||||
|
|
||||||
|
elem->prev->next = elem->next;
|
||||||
|
elem->next->prev = elem->prev;
|
||||||
|
}
|
226
linked-list/sc_list.h
Normal file
226
linked-list/sc_list.h
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020 Ozan Tezcan
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef SC_LIST_H
|
||||||
|
#define SC_LIST_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
|
||||||
|
struct sc_list
|
||||||
|
{
|
||||||
|
struct sc_list *next;
|
||||||
|
struct sc_list *prev;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define sc_list_entry(ptr, type, elem) \
|
||||||
|
((type *) ((char *) (ptr) -offsetof(type, elem)))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param list list pointer
|
||||||
|
*/
|
||||||
|
void sc_list_init(struct sc_list *list);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param list list pointer
|
||||||
|
*/
|
||||||
|
void sc_list_clear(struct sc_list *list);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param list list pointer
|
||||||
|
* @return 'true' if empty, false otherwise
|
||||||
|
*/
|
||||||
|
bool sc_list_is_empty(struct sc_list *list);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param list list pointer
|
||||||
|
* @return element count in the list, beware this is a log(n) operation.
|
||||||
|
*/
|
||||||
|
size_t sc_list_count(struct sc_list *list);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param list list pointer
|
||||||
|
* @return returns head. If list is empty, returns NULL.
|
||||||
|
*/
|
||||||
|
struct sc_list *sc_list_head(struct sc_list *list);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param list list pointer
|
||||||
|
* @return returns tail. If list is empty, returns NULL.
|
||||||
|
*/
|
||||||
|
struct sc_list *sc_list_tail(struct sc_list *list);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param list list pointer
|
||||||
|
* @param elem elem to add to head
|
||||||
|
*/
|
||||||
|
void sc_list_add_head(struct sc_list *list, struct sc_list *elem);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* before : [head]item1 -> item2 -> item3
|
||||||
|
* after : [head]item2 -> item3
|
||||||
|
*
|
||||||
|
* @param list list pointer
|
||||||
|
* @return head element, if list is empty, returns NULL.
|
||||||
|
*/
|
||||||
|
struct sc_list *sc_list_pop_head(struct sc_list *list);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* before : [head]item1 -> [tail]item2
|
||||||
|
* after : [head]item1 -> item2 -> [tail]'elem'
|
||||||
|
*
|
||||||
|
* @param list
|
||||||
|
* @param elem
|
||||||
|
*/
|
||||||
|
void sc_list_add_tail(struct sc_list *list, struct sc_list *elem);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* before : [head]item1 -> item2 -> item3
|
||||||
|
* after : [head]item2 -> item2
|
||||||
|
*
|
||||||
|
* @param list list pointer
|
||||||
|
* @return head element, if list is empty, returns NULL.
|
||||||
|
*/
|
||||||
|
struct sc_list *sc_list_pop_tail(struct sc_list *list);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* before : item1 -> 'prev' -> item2
|
||||||
|
* after : item1 -> 'prev'-> 'elem' -> item2
|
||||||
|
*
|
||||||
|
* @param list list pointer
|
||||||
|
* @param prev previous element of the 'elem'
|
||||||
|
* @param elem elem to be added after 'prev'
|
||||||
|
*/
|
||||||
|
void sc_list_add_after(struct sc_list *list, struct sc_list *prev,
|
||||||
|
struct sc_list *elem);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* before : item1 -> 'next' -> item2
|
||||||
|
* after : item1 -> 'elem'-> 'next' -> item2
|
||||||
|
*
|
||||||
|
* @param list list pointer
|
||||||
|
* @param next next element of the 'elem'
|
||||||
|
* @param elem elem to be added before 'next'
|
||||||
|
*/
|
||||||
|
void sc_list_add_before(struct sc_list *list, struct sc_list *next,
|
||||||
|
struct sc_list *elem);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* before : item1 -> 'elem' -> item2
|
||||||
|
* after : item1 -> item2
|
||||||
|
*
|
||||||
|
* @param list list pointer
|
||||||
|
* @param elem elem to be deleted
|
||||||
|
*/
|
||||||
|
void sc_list_del(struct sc_list *list, struct sc_list *elem);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* struct container {
|
||||||
|
* struct sc_list others;
|
||||||
|
* };
|
||||||
|
*
|
||||||
|
* struct container *container; // User object
|
||||||
|
* struct sc_list *list; // List pointer, should already be initialized.
|
||||||
|
* struct sc_list *it; // Iterator
|
||||||
|
*
|
||||||
|
* sc_list_foreach(list, it) {
|
||||||
|
* container = sc_list_entry(it, struct container, others);
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#define sc_list_foreach(list, elem) \
|
||||||
|
for ((elem) = (list)->next; (elem) != (list); (elem) = (elem)->next)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* It is safe to delete items from the list while using
|
||||||
|
* this iterator.
|
||||||
|
*
|
||||||
|
* struct container {
|
||||||
|
* struct sc_list others;
|
||||||
|
* };
|
||||||
|
*
|
||||||
|
* struct container *container; // User object
|
||||||
|
* struct sc_list *list; // List pointer, should already be initialized.
|
||||||
|
* struct sc_list *it; // Iterator
|
||||||
|
*
|
||||||
|
* sc_list_foreach(list, it) {
|
||||||
|
* container = sc_list_entry(it, struct container, others);
|
||||||
|
* sc_list_del(list, &container->others);
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#define sc_list_foreach_safe(list, n, elem) \
|
||||||
|
for ((elem) = (list)->next, (n) = (elem)->next; (elem) != (list); \
|
||||||
|
(elem) = (n), (n) = (elem)->next)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse iterator
|
||||||
|
*
|
||||||
|
* struct container {
|
||||||
|
* struct sc_list others;
|
||||||
|
* };
|
||||||
|
*
|
||||||
|
* struct container *container; // User object
|
||||||
|
* struct sc_list *list; // List pointer, should already be initialized.
|
||||||
|
* struct sc_list *it; // Iterator
|
||||||
|
*
|
||||||
|
* sc_list_foreach(list, it) {
|
||||||
|
* container = sc_list_entry(it, struct container, others);
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define sc_list_foreach_r(list, elem) \
|
||||||
|
for ((elem) = (list)->prev; (elem) != (list); (elem) = (elem)->prev)
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse iterator.
|
||||||
|
*
|
||||||
|
* It is safe to delete items from the list while using
|
||||||
|
* this iterator.
|
||||||
|
*
|
||||||
|
* struct container {
|
||||||
|
* struct sc_list others;
|
||||||
|
* };
|
||||||
|
*
|
||||||
|
* struct container *container; // User object
|
||||||
|
* struct sc_list *list; // List pointer, should already be initialized.
|
||||||
|
* struct sc_list *it; // Iterator
|
||||||
|
*
|
||||||
|
* sc_list_foreach(list, it) {
|
||||||
|
* container = sc_list_entry(it, struct container, others);
|
||||||
|
* sc_list_del(list, &container->others);
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#define sc_list_foreach_safe_r(list, n, elem) \
|
||||||
|
for ((elem) = (list)->prev, (n) = (elem)->prev; (elem) != (list); \
|
||||||
|
(elem) = (n), (n) = (elem)->prev)
|
||||||
|
|
||||||
|
#endif
|
94
logger/CMakeLists.txt
Normal file
94
logger/CMakeLists.txt
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.5.1)
|
||||||
|
project(sc_log C)
|
||||||
|
|
||||||
|
set(CMAKE_C_STANDARD 99)
|
||||||
|
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||||
|
set(CMAKE_C_EXTENSIONS OFF)
|
||||||
|
|
||||||
|
add_executable(sc_log log_example.c sc_log.h sc_log.c)
|
||||||
|
|
||||||
|
if (NOT CMAKE_C_COMPILER_ID MATCHES "MSVC")
|
||||||
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -Wall -pedantic -Werror -D_GNU_SOURCE -pthread")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# --------------------- Test Configuration Start ---------------------------- #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
|
||||||
|
include(CTest)
|
||||||
|
include(CheckCCompilerFlag)
|
||||||
|
|
||||||
|
enable_testing()
|
||||||
|
|
||||||
|
add_executable(${PROJECT_NAME}_test log_test.c sc_log.c)
|
||||||
|
|
||||||
|
if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||||
|
if ("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR
|
||||||
|
"${CMAKE_C_COMPILER_ID}" STREQUAL "GNU")
|
||||||
|
|
||||||
|
target_compile_options(${PROJECT_NAME}_test PRIVATE -DSC_HAVE_WRAP)
|
||||||
|
target_compile_options(${PROJECT_NAME}_test PRIVATE -fno-builtin)
|
||||||
|
target_link_options(${PROJECT_NAME}_test PRIVATE
|
||||||
|
-Wl,--wrap=fprintf,--wrap=vfprintf,--wrap=fopen,--wrap=localtime,--wrap=pthread_mutexattr_init)
|
||||||
|
endif ()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
if ("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR
|
||||||
|
"${CMAKE_C_COMPILER_ID}" STREQUAL "AppleClang" OR
|
||||||
|
"${CMAKE_C_COMPILER_ID}" STREQUAL "GNU")
|
||||||
|
|
||||||
|
target_compile_options(${PROJECT_NAME}_test PRIVATE -fno-omit-frame-pointer)
|
||||||
|
|
||||||
|
if (SANITIZER)
|
||||||
|
target_compile_options(${PROJECT_NAME}_test PRIVATE -fsanitize=${SANITIZER})
|
||||||
|
target_link_options(${PROJECT_NAME}_test PRIVATE -fsanitize=${SANITIZER})
|
||||||
|
endif ()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
add_test(NAME ${PROJECT_NAME}_test COMMAND ${PROJECT_NAME}_test)
|
||||||
|
|
||||||
|
SET(MEMORYCHECK_COMMAND_OPTIONS
|
||||||
|
"-q --log-fd=2 --trace-children=yes --track-origins=yes \
|
||||||
|
--leak-check=full --show-leak-kinds=all --show-reachable=yes \
|
||||||
|
--error-exitcode=255")
|
||||||
|
|
||||||
|
add_custom_target(valgrind_${PROJECT_NAME} ${CMAKE_COMMAND}
|
||||||
|
-E env CTEST_OUTPUT_ON_FAILURE=1
|
||||||
|
${CMAKE_CTEST_COMMAND} -C $<CONFIG>
|
||||||
|
--overwrite MemoryCheckCommandOptions=${MEMORYCHECK_COMMAND_OPTIONS}
|
||||||
|
--verbose -T memcheck WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||||
|
|
||||||
|
add_custom_target(check_${PROJECT_NAME} ${CMAKE_COMMAND}
|
||||||
|
-E env CTEST_OUTPUT_ON_FAILURE=1
|
||||||
|
${CMAKE_CTEST_COMMAND} -C $<CONFIG> --verbose
|
||||||
|
WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||||
|
|
||||||
|
# ----------------------- - Code Coverage Start ----------------------------- #
|
||||||
|
|
||||||
|
if (${CMAKE_BUILD_TYPE} MATCHES "Coverage")
|
||||||
|
if ("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU")
|
||||||
|
target_compile_options(${PROJECT_NAME}_test PRIVATE --coverage)
|
||||||
|
target_link_libraries(${PROJECT_NAME}_test gcov)
|
||||||
|
else()
|
||||||
|
message(FATAL_ERROR "Only GCC is supported for coverage")
|
||||||
|
endif()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
add_custom_target(coverage_${PROJECT_NAME})
|
||||||
|
add_custom_command(
|
||||||
|
TARGET coverage_${PROJECT_NAME}
|
||||||
|
COMMAND lcov --capture --directory ..
|
||||||
|
--output-file coverage.info --rc lcov_branch_coverage=1
|
||||||
|
COMMAND lcov --remove coverage.info '/usr/*' '*example*' '*test*'
|
||||||
|
--output-file coverage.info --rc lcov_branch_coverage=1
|
||||||
|
COMMAND lcov --list coverage.info --rc lcov_branch_coverage=1
|
||||||
|
)
|
||||||
|
|
||||||
|
add_dependencies(coverage_${PROJECT_NAME} check_${PROJECT_NAME})
|
||||||
|
|
||||||
|
# -------------------------- Code Coverage End ------------------------------ #
|
||||||
|
|
||||||
|
|
||||||
|
# ----------------------- Test Configuration End ---------------------------- #
|
||||||
|
|
59
logger/README.md
Normal file
59
logger/README.md
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
# Logger
|
||||||
|
|
||||||
|
### Overview
|
||||||
|
|
||||||
|
- Log destination can be stdout, file and user callback.
|
||||||
|
- Possible to get logs to all destinations at the same time.
|
||||||
|
- Log files are rotated.
|
||||||
|
- Thread-safe.
|
||||||
|
- Just copy <b>sc_log.h</b> and <b>sc_log.c</b> to your project.
|
||||||
|
|
||||||
|
#### Usage
|
||||||
|
|
||||||
|
|
||||||
|
```c
|
||||||
|
#include "sc_log.h"
|
||||||
|
|
||||||
|
int log_callback(void *arg, enum sc_log_level level,
|
||||||
|
const char *fmt, va_list va)
|
||||||
|
{
|
||||||
|
const char *my_app = arg;
|
||||||
|
const char *level_str = sc_log_levels[level].str;
|
||||||
|
|
||||||
|
fprintf(stdout, " %s received log : level = [%s] ", my_app, level_str);
|
||||||
|
vfprintf(stdout, fmt, va);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
const char* my_app_name = "my app";
|
||||||
|
|
||||||
|
//sc_log_init(); is not thread-safe, it must be called by a single thread.
|
||||||
|
sc_log_init();
|
||||||
|
|
||||||
|
//Default log-level is 'info' and default destination is 'stdout'
|
||||||
|
sc_log_info("Hello world!");
|
||||||
|
|
||||||
|
//Enable logging to file.
|
||||||
|
sc_log_set_file("log.0.txt", "log-latest.txt");
|
||||||
|
|
||||||
|
//stdout and file will get the log line
|
||||||
|
sc_log_info("to stdout and file!");
|
||||||
|
|
||||||
|
//Enable callback
|
||||||
|
sc_log_set_callback(log_callback, (void*) my_app_name);
|
||||||
|
|
||||||
|
//stdout, file and callback will get the log line
|
||||||
|
sc_log_info("to all!");
|
||||||
|
sc_log_info("to all!");
|
||||||
|
|
||||||
|
//sc_log_term(); is not thread-safe, it must be called by a single thread.
|
||||||
|
sc_log_term();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
42
logger/log_example.c
Normal file
42
logger/log_example.c
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
#include "sc_log.h"
|
||||||
|
|
||||||
|
int log_callback(void *arg, enum sc_log_level level,
|
||||||
|
const char *fmt, va_list va)
|
||||||
|
{
|
||||||
|
const char *my_app = arg;
|
||||||
|
const char *level_str = sc_log_levels[level].str;
|
||||||
|
|
||||||
|
fprintf(stdout, " %s received log : level = [%s] ", my_app, level_str);
|
||||||
|
vfprintf(stdout, fmt, va);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
const char* my_app_name = "my app";
|
||||||
|
|
||||||
|
//sc_log_init(); is not thread-safe, it must be called by a single thread.
|
||||||
|
sc_log_init();
|
||||||
|
|
||||||
|
//Default log-level is 'info' and default destination is 'stdout'
|
||||||
|
sc_log_info("Hello world!");
|
||||||
|
|
||||||
|
//Enable logging to file.
|
||||||
|
sc_log_set_file("log.0.txt", "log-latest.txt");
|
||||||
|
|
||||||
|
//stdout and file will get the log line
|
||||||
|
sc_log_info("to stdout and file!");
|
||||||
|
|
||||||
|
//Enable callback
|
||||||
|
sc_log_set_callback(log_callback, (void*) my_app_name);
|
||||||
|
|
||||||
|
//stdout, file and callback will get the log line
|
||||||
|
sc_log_info("to all!");
|
||||||
|
sc_log_info("to all!");
|
||||||
|
|
||||||
|
//sc_log_term(); is not thread-safe, it must be called by a single thread.
|
||||||
|
sc_log_term();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
305
logger/log_test.c
Normal file
305
logger/log_test.c
Normal file
@ -0,0 +1,305 @@
|
|||||||
|
#define SC_LOG_PRINT_FILE_NAME
|
||||||
|
#include "sc_log.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
|
||||||
|
int callback(void *arg, enum sc_log_level level, const char *fmt, va_list va)
|
||||||
|
{
|
||||||
|
*(int *) arg = *(int *) arg + 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void test1(void)
|
||||||
|
{
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
sc_log_init();
|
||||||
|
sc_log_set_callback(callback, &count);
|
||||||
|
assert(sc_log_set_level("errrorr") == -1);
|
||||||
|
sc_log_debug("test");
|
||||||
|
assert(count == 0);
|
||||||
|
|
||||||
|
sc_log_set_level("DEBUG");
|
||||||
|
sc_log_debug("test");
|
||||||
|
assert(count == 1);
|
||||||
|
sc_log_info("test");
|
||||||
|
assert(count == 2);
|
||||||
|
sc_log_warn("test");
|
||||||
|
assert(count == 3);
|
||||||
|
sc_log_error("test");
|
||||||
|
assert(count == 4);
|
||||||
|
|
||||||
|
count = 0;
|
||||||
|
sc_log_set_level("INFO");
|
||||||
|
sc_log_debug("test");
|
||||||
|
assert(count == 0);
|
||||||
|
sc_log_info("test");
|
||||||
|
assert(count == 1);
|
||||||
|
sc_log_warn("test");
|
||||||
|
assert(count == 2);
|
||||||
|
sc_log_error("test");
|
||||||
|
assert(count == 3);
|
||||||
|
|
||||||
|
count = 0;
|
||||||
|
sc_log_set_level("WARN");
|
||||||
|
sc_log_debug("test");
|
||||||
|
assert(count == 0);
|
||||||
|
sc_log_info("test");
|
||||||
|
assert(count == 0);
|
||||||
|
sc_log_warn("test");
|
||||||
|
assert(count == 1);
|
||||||
|
sc_log_error("test");
|
||||||
|
assert(count == 2);
|
||||||
|
|
||||||
|
count = 0;
|
||||||
|
sc_log_set_level("OFF");
|
||||||
|
sc_log_debug("test");
|
||||||
|
assert(count == 0);
|
||||||
|
sc_log_info("test");
|
||||||
|
assert(count == 0);
|
||||||
|
sc_log_warn("test");
|
||||||
|
assert(count == 0);
|
||||||
|
sc_log_error("test");
|
||||||
|
assert(count == 0);
|
||||||
|
|
||||||
|
sc_log_set_level("INFO");
|
||||||
|
sc_log_set_stdout(false);
|
||||||
|
assert(sc_log_set_file("prev.txt", "current.txt") == 0);
|
||||||
|
for (int i = 0; i < 100000; i++) {
|
||||||
|
sc_log_error("testtesttesttesttesttesttesttesttesttesttesttest");
|
||||||
|
}
|
||||||
|
|
||||||
|
FILE *fp = fopen("prev.txt", "rb");
|
||||||
|
assert(fp != NULL);
|
||||||
|
fseek(fp, 0, SEEK_END);
|
||||||
|
assert(ftell(fp) >= SC_LOG_FILE_SIZE);
|
||||||
|
fclose(fp);
|
||||||
|
|
||||||
|
sc_log_term();
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef SC_HAVE_WRAP
|
||||||
|
|
||||||
|
#include <pthread.h>
|
||||||
|
|
||||||
|
int callback2(void *arg, enum sc_log_level level, const char *fmt, va_list va)
|
||||||
|
{
|
||||||
|
vfprintf(stdout, fmt, va);
|
||||||
|
vfprintf(stdout, fmt, va);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool mock_fprintf = false;
|
||||||
|
FILE *fprintf_file;
|
||||||
|
int fprintf_count = 0;
|
||||||
|
int fprintf_ret = 0;
|
||||||
|
extern int __real_fprintf(FILE *stream, const char *format, ...);
|
||||||
|
int __wrap_fprintf(FILE *stream, const char *format, ...)
|
||||||
|
{
|
||||||
|
int rc;
|
||||||
|
va_list va;
|
||||||
|
|
||||||
|
if (!mock_fprintf) {
|
||||||
|
va_start(va, format);
|
||||||
|
rc = vfprintf(stream, format, va);
|
||||||
|
va_end(va);
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf_file = stream;
|
||||||
|
fprintf_count++;
|
||||||
|
return fprintf_ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool mock_vfprintf = false;
|
||||||
|
FILE *vprintf_file;
|
||||||
|
int vfprintf_count = 0;
|
||||||
|
int vfprintf_ret = 0;
|
||||||
|
extern int __real_vfprintf(FILE *stream, const char *format, va_list arg);
|
||||||
|
int __wrap_vfprintf(FILE *stream, const char *format, va_list arg)
|
||||||
|
{
|
||||||
|
if (!mock_vfprintf) {
|
||||||
|
return __real_vfprintf(stream, format, arg);
|
||||||
|
}
|
||||||
|
vprintf_file = stream;
|
||||||
|
vfprintf_count++;
|
||||||
|
return vfprintf_ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool mock_fopen = false;
|
||||||
|
extern FILE *__real_fopen(const char *filename, const char *format);
|
||||||
|
FILE *__wrap_fopen(const char *filename, const char *mode)
|
||||||
|
{
|
||||||
|
if (!mock_fopen) {
|
||||||
|
return __real_fopen(filename, mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool mock_localtime = false;
|
||||||
|
extern struct tm *__real_localtime(const time_t *timer);
|
||||||
|
struct tm *__wrap_localtime(const time_t *timer)
|
||||||
|
{
|
||||||
|
if (!mock_localtime) {
|
||||||
|
return __real_localtime(timer);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool mock_attrinit = false;
|
||||||
|
extern int __real_pthread_mutexattr_init(pthread_mutexattr_t *attr);
|
||||||
|
int __wrap_pthread_mutexattr_init(pthread_mutexattr_t *attr)
|
||||||
|
{
|
||||||
|
if (!mock_attrinit) {
|
||||||
|
return __real_pthread_mutexattr_init(attr);
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void fail_test(void)
|
||||||
|
{
|
||||||
|
mock_attrinit = true;
|
||||||
|
assert(sc_log_init() < 0);
|
||||||
|
mock_attrinit = false;
|
||||||
|
assert(sc_log_init() == 0);
|
||||||
|
|
||||||
|
mock_fprintf = true;
|
||||||
|
mock_vfprintf = true;
|
||||||
|
|
||||||
|
vfprintf_count = 0;
|
||||||
|
sc_log_info("loggg");
|
||||||
|
assert(vfprintf_count > 0);
|
||||||
|
assert(vprintf_file == stdout);
|
||||||
|
|
||||||
|
vfprintf_count = 0;
|
||||||
|
sc_log_set_stdout(false);
|
||||||
|
sc_log_info("loggg");
|
||||||
|
assert(vfprintf_count == 0);
|
||||||
|
|
||||||
|
sc_log_set_stdout(true);
|
||||||
|
sc_log_set_file("tmp.txt", "tmp2.txt");
|
||||||
|
sc_log_set_callback(callback2, NULL);
|
||||||
|
|
||||||
|
fprintf_count = 0;
|
||||||
|
vfprintf_count = 0;
|
||||||
|
sc_log_info("loggg");
|
||||||
|
assert(vfprintf_count + fprintf_count == 6);
|
||||||
|
sc_log_set_callback(NULL, NULL);
|
||||||
|
|
||||||
|
fprintf_count = 0;
|
||||||
|
vfprintf_count = 0;
|
||||||
|
sc_log_set_stdout(false);
|
||||||
|
sc_log_set_file(NULL, NULL);
|
||||||
|
sc_log_info("loggg");
|
||||||
|
assert(vfprintf_count + fprintf_count == 0);
|
||||||
|
|
||||||
|
sc_log_set_stdout(true);
|
||||||
|
fprintf_ret = -1;
|
||||||
|
assert(sc_log_info("test") == -1);
|
||||||
|
fprintf_ret = 0;
|
||||||
|
assert(sc_log_info("test") == 0);
|
||||||
|
|
||||||
|
sc_log_set_stdout(false);
|
||||||
|
sc_log_set_file("tmp.txt", "tmp2.txt");
|
||||||
|
vfprintf_ret = -1;
|
||||||
|
assert(sc_log_info("test") == -1);
|
||||||
|
vfprintf_ret = 0;
|
||||||
|
assert(sc_log_info("test") == 0);
|
||||||
|
fprintf_ret = -1;
|
||||||
|
assert(sc_log_info("test") == -1);
|
||||||
|
fprintf_ret = 0;
|
||||||
|
assert(sc_log_info("test") == 0);
|
||||||
|
fprintf_ret = -1;
|
||||||
|
assert(sc_log_set_file("tmp.txt", "tmp2.txt") == -1);
|
||||||
|
fprintf_ret = 0;
|
||||||
|
|
||||||
|
assert(sc_log_set_file(NULL, "test.txt") == 0);
|
||||||
|
mock_fopen = true;
|
||||||
|
assert(sc_log_set_file("prev.txt", "current.txt") == -1);
|
||||||
|
mock_fopen = false;
|
||||||
|
assert(sc_log_set_file("prev.txt", "current.txt") == 0);
|
||||||
|
mock_localtime = true;
|
||||||
|
assert(sc_log_error("test") == -1);
|
||||||
|
mock_localtime = false;
|
||||||
|
|
||||||
|
mock_vfprintf = false;
|
||||||
|
mock_fprintf = false;
|
||||||
|
mock_fopen = true;
|
||||||
|
int failed = 0;
|
||||||
|
for (int i = 0; i < 40000; i++) {
|
||||||
|
failed = sc_log_error("testtesttesttesttesttesttesttesttesttestest");
|
||||||
|
if (failed < 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert(failed == -1);
|
||||||
|
mock_fopen = false;
|
||||||
|
|
||||||
|
sc_log_term();
|
||||||
|
mock_fprintf = false;
|
||||||
|
mock_vfprintf = false;
|
||||||
|
mock_localtime = false;
|
||||||
|
mock_fopen = false;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
void fail_test(void)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int log_callback(void *arg, enum sc_log_level level,
|
||||||
|
const char *fmt, va_list va)
|
||||||
|
{
|
||||||
|
const char *my_app = arg;
|
||||||
|
const char *level_str = sc_log_levels[level].str;
|
||||||
|
|
||||||
|
fprintf(stdout, " %s received log : level = [%s] ", my_app, level_str);
|
||||||
|
vfprintf(stdout, fmt, va);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void example(void)
|
||||||
|
{
|
||||||
|
const char* my_app_name = "my app";
|
||||||
|
|
||||||
|
//sc_log_init() is not thread-safe, it must be called by a single thread.
|
||||||
|
sc_log_init();
|
||||||
|
|
||||||
|
//Default log-level is 'info' and default destination is 'stdout'
|
||||||
|
sc_log_info("Hello world!");
|
||||||
|
|
||||||
|
//Enable logging to file.
|
||||||
|
sc_log_set_file("log.0.txt", "log-latest.txt");
|
||||||
|
|
||||||
|
//stdout and file will get the log line
|
||||||
|
sc_log_info("to stdout and file!");
|
||||||
|
|
||||||
|
//Enable callback
|
||||||
|
sc_log_set_callback(log_callback, (void*) my_app_name);
|
||||||
|
|
||||||
|
//stdout, file and callback will get the log line
|
||||||
|
sc_log_info("to all!");
|
||||||
|
sc_log_info("to all!");
|
||||||
|
|
||||||
|
//sc_log_term(); is not thread-safe, it must be called by a single thread.
|
||||||
|
sc_log_term();
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
fail_test();
|
||||||
|
example();
|
||||||
|
test1();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
326
logger/sc_log.c
Normal file
326
logger/sc_log.c
Normal file
@ -0,0 +1,326 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020 Ozan Tezcan
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "sc_log.h"
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
#if defined(_WIN32) || defined(_WIN64)
|
||||||
|
|
||||||
|
#pragma warning(disable : 4996)
|
||||||
|
#define strcasecmp _stricmp
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
struct sc_log_mutex
|
||||||
|
{
|
||||||
|
CRITICAL_SECTION mtx;
|
||||||
|
};
|
||||||
|
|
||||||
|
int sc_log_mutex_init(struct sc_log_mutex *mtx)
|
||||||
|
{
|
||||||
|
InitializeCriticalSection(&mtx->mtx);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int sc_log_mutex_term(struct sc_log_mutex *mtx)
|
||||||
|
{
|
||||||
|
DeleteCriticalSection(&mtx->mtx);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sc_log_mutex_lock(struct sc_log_mutex *mtx)
|
||||||
|
{
|
||||||
|
EnterCriticalSection(&mtx->mtx);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sc_log_mutex_unlock(struct sc_log_mutex *mtx)
|
||||||
|
{
|
||||||
|
LeaveCriticalSection(&mtx->mtx);
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
#include <pthread.h>
|
||||||
|
|
||||||
|
struct sc_log_mutex
|
||||||
|
{
|
||||||
|
pthread_mutex_t mtx;
|
||||||
|
};
|
||||||
|
|
||||||
|
int sc_log_mutex_init(struct sc_log_mutex *mtx)
|
||||||
|
{
|
||||||
|
pthread_mutexattr_t attr;
|
||||||
|
pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER;
|
||||||
|
|
||||||
|
mtx->mtx = mut;
|
||||||
|
|
||||||
|
if (pthread_mutexattr_init(&attr) != 0 ||
|
||||||
|
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL) != 0 ||
|
||||||
|
pthread_mutex_init(&mtx->mtx, &attr) != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_mutexattr_destroy(&attr);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int sc_log_mutex_term(struct sc_log_mutex *mtx)
|
||||||
|
{
|
||||||
|
return pthread_mutex_destroy(&mtx->mtx);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sc_log_mutex_lock(struct sc_log_mutex *mtx)
|
||||||
|
{
|
||||||
|
pthread_mutex_lock(&mtx->mtx);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sc_log_mutex_unlock(struct sc_log_mutex *mtx)
|
||||||
|
{
|
||||||
|
pthread_mutex_unlock(&mtx->mtx);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct sc_log
|
||||||
|
{
|
||||||
|
FILE *fp;
|
||||||
|
const char *current_file;
|
||||||
|
const char *prev_file;
|
||||||
|
size_t file_size;
|
||||||
|
|
||||||
|
struct sc_log_mutex mtx;
|
||||||
|
enum sc_log_level level;
|
||||||
|
|
||||||
|
bool to_stdout;
|
||||||
|
sc_log_callback cb;
|
||||||
|
void *arg;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct sc_log sc_log;
|
||||||
|
|
||||||
|
int sc_log_init(void)
|
||||||
|
{
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
rc = sc_log_mutex_init(&sc_log.mtx);
|
||||||
|
if (rc != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_log.level = SC_LOG_INFO;
|
||||||
|
sc_log.to_stdout = true;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int sc_log_term(void)
|
||||||
|
{
|
||||||
|
int rc = 0;
|
||||||
|
|
||||||
|
if (sc_log.fp) {
|
||||||
|
rc = fclose(sc_log.fp);
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_log_mutex_term(&sc_log.mtx);
|
||||||
|
sc_log = (struct sc_log){0};
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
int sc_log_set_level(const char *str)
|
||||||
|
{
|
||||||
|
size_t count = sizeof(sc_log_levels) / sizeof(struct sc_log_level_pair);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < count; i++) {
|
||||||
|
if (strcasecmp(str, sc_log_levels[i].str) == 0) {
|
||||||
|
sc_log_mutex_lock(&sc_log.mtx);
|
||||||
|
sc_log.level = sc_log_levels[i].id;
|
||||||
|
sc_log_mutex_unlock(&sc_log.mtx);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int sc_log_set_stdout(bool enable)
|
||||||
|
{
|
||||||
|
sc_log_mutex_lock(&sc_log.mtx);
|
||||||
|
sc_log.to_stdout = enable;
|
||||||
|
sc_log_mutex_unlock(&sc_log.mtx);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int sc_log_set_file(const char *prev_file, const char *current_file)
|
||||||
|
{
|
||||||
|
int rc = 0;
|
||||||
|
long size;
|
||||||
|
FILE *fp = NULL;
|
||||||
|
|
||||||
|
sc_log_mutex_lock(&sc_log.mtx);
|
||||||
|
|
||||||
|
if (sc_log.fp != NULL) {
|
||||||
|
rc = fclose(sc_log.fp);
|
||||||
|
sc_log.fp = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_log.prev_file = prev_file;
|
||||||
|
sc_log.current_file = current_file;
|
||||||
|
|
||||||
|
if (prev_file == NULL || current_file == NULL) {
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
fp = fopen(sc_log.current_file, "a+");
|
||||||
|
if (fp == NULL || fprintf(fp, "\n") < 0 || (size = ftell(fp)) < 0) {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_log.file_size = size;
|
||||||
|
sc_log.fp = fp;
|
||||||
|
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
error:
|
||||||
|
rc = -1;
|
||||||
|
if (fp != NULL) {
|
||||||
|
fclose(fp);
|
||||||
|
}
|
||||||
|
out:
|
||||||
|
sc_log_mutex_unlock(&sc_log.mtx);
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
int sc_log_set_callback(sc_log_callback cb, void *arg)
|
||||||
|
{
|
||||||
|
sc_log_mutex_lock(&sc_log.mtx);
|
||||||
|
sc_log.cb = cb;
|
||||||
|
sc_log.arg = arg;
|
||||||
|
sc_log_mutex_unlock(&sc_log.mtx);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int sc_log_print_header(FILE *fp, enum sc_log_level level)
|
||||||
|
{
|
||||||
|
time_t t = time(NULL);
|
||||||
|
struct tm *tm = localtime(&t);
|
||||||
|
|
||||||
|
if (tm == NULL) {
|
||||||
|
fprintf(fp, "[ERROR] localtime() returns null! \n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return fprintf(fp, "[%d-%02d-%02d %02d:%02d:%02d][%-5s] ",
|
||||||
|
tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour,
|
||||||
|
tm->tm_min, tm->tm_sec, sc_log_levels[level].str);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int sc_log_stdout(enum sc_log_level level, const char *fmt, va_list va)
|
||||||
|
{
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
rc = sc_log_print_header(stdout, level);
|
||||||
|
if (rc < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return vfprintf(stdout, fmt, va);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int sc_log_file(enum sc_log_level level, const char *fmt, va_list va)
|
||||||
|
{
|
||||||
|
int rc, size;
|
||||||
|
|
||||||
|
rc = sc_log_print_header(sc_log.fp, level);
|
||||||
|
if (rc < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
size = vfprintf(sc_log.fp, fmt, va);
|
||||||
|
if (size < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_log.file_size += size;
|
||||||
|
|
||||||
|
if (sc_log.file_size > SC_LOG_FILE_SIZE) {
|
||||||
|
fclose(sc_log.fp);
|
||||||
|
(void) rename(sc_log.current_file, sc_log.prev_file);
|
||||||
|
|
||||||
|
sc_log.fp = fopen(sc_log.current_file, "w+");
|
||||||
|
if (sc_log.fp == NULL) {
|
||||||
|
fprintf(stderr, "fopen() failed for [%s], (%s)\n",
|
||||||
|
sc_log.current_file, strerror(errno));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_log.file_size = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
int sc_log_log(enum sc_log_level level, const char *fmt, ...)
|
||||||
|
{
|
||||||
|
int rc = 0;
|
||||||
|
va_list va;
|
||||||
|
|
||||||
|
sc_log_mutex_lock(&sc_log.mtx);
|
||||||
|
|
||||||
|
if (level < sc_log.level) {
|
||||||
|
sc_log_mutex_unlock(&sc_log.mtx);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sc_log.to_stdout) {
|
||||||
|
va_start(va, fmt);
|
||||||
|
rc |= sc_log_stdout(level, fmt, va);
|
||||||
|
va_end(va);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sc_log.fp != NULL) {
|
||||||
|
va_start(va, fmt);
|
||||||
|
rc |= sc_log_file(level, fmt, va);
|
||||||
|
va_end(va);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sc_log.cb) {
|
||||||
|
va_start(va, fmt);
|
||||||
|
rc |= sc_log.cb(sc_log.arg, level, fmt, va);
|
||||||
|
va_end(va);
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_log_mutex_unlock(&sc_log.mtx);
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
157
logger/sc_log.h
Normal file
157
logger/sc_log.h
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020 Ozan Tezcan
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef SC_LOG_H
|
||||||
|
#define SC_LOG_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
enum sc_log_level
|
||||||
|
{
|
||||||
|
SC_LOG_DEBUG,
|
||||||
|
SC_LOG_INFO,
|
||||||
|
SC_LOG_WARN,
|
||||||
|
SC_LOG_ERROR,
|
||||||
|
SC_LOG_OFF,
|
||||||
|
};
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
const static struct sc_log_level_pair
|
||||||
|
{
|
||||||
|
const int id;
|
||||||
|
const char *str;
|
||||||
|
} sc_log_levels[] = {
|
||||||
|
{SC_LOG_DEBUG, "DEBUG"},
|
||||||
|
{SC_LOG_INFO, "INFO" },
|
||||||
|
{SC_LOG_WARN, "WARN" },
|
||||||
|
{SC_LOG_ERROR, "ERROR"},
|
||||||
|
{SC_LOG_OFF, "OFF" },
|
||||||
|
};
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User callback
|
||||||
|
*
|
||||||
|
* @param arg user provided data
|
||||||
|
* @param level log level
|
||||||
|
* @param fmt format
|
||||||
|
* @param va arg list
|
||||||
|
*/
|
||||||
|
typedef int (*sc_log_callback)(void *arg, enum sc_log_level level,
|
||||||
|
const char *fmt, va_list va);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Not thread-safe, should be called from a single thread.
|
||||||
|
* @return '0' on success, negative value on error
|
||||||
|
*/
|
||||||
|
int sc_log_init(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Not thread-safe, should be called from a single thread.
|
||||||
|
* @return '0' on success, negative value on error
|
||||||
|
*/
|
||||||
|
int sc_log_term(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thread-safe.
|
||||||
|
*
|
||||||
|
* Valid values are 'DEBUG', 'INFO', 'WARN', 'ERROR', 'OFF'
|
||||||
|
* @param level_str level
|
||||||
|
* @return '0' on success, negative value on error
|
||||||
|
*/
|
||||||
|
int sc_log_set_level(const char *level_str);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thread-safe.
|
||||||
|
*
|
||||||
|
* @param enable 'true' to enable, 'false' will disable
|
||||||
|
* @return '0' on success, negative value on error
|
||||||
|
*/
|
||||||
|
int sc_log_set_stdout(bool enable);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thread-safe.
|
||||||
|
*
|
||||||
|
* Log file will be rotated. Logger will start writing into 'current_file',
|
||||||
|
* when it grows larger than 'SC_LOG_FILE_SIZE', logger will rename
|
||||||
|
* 'current_file' as 'prev_file' and create a new empty file at 'current_file'
|
||||||
|
* again. So, latest logs will always be in the 'current_file'.
|
||||||
|
*
|
||||||
|
* e.g sc_log_set_file("/tmp/log.0.txt", "/tmp/log-latest.txt");
|
||||||
|
*
|
||||||
|
* To disable logging into file :
|
||||||
|
*
|
||||||
|
* sc_log_set_file(NULL, NULL);
|
||||||
|
*
|
||||||
|
* @param prev_file file path for previous log file, 'NULL' to disable
|
||||||
|
* @param current_file file path for latest log file, 'NULL' to disable
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
int sc_log_set_file(const char *prev_file, const char *current_file);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thread-safe.
|
||||||
|
* Logs can be reported to callback as well.
|
||||||
|
*
|
||||||
|
* @param cb callback.
|
||||||
|
* @param arg user arg.
|
||||||
|
* @return '0' on success, negative value on error
|
||||||
|
*/
|
||||||
|
int sc_log_set_callback(sc_log_callback cb, void *arg);
|
||||||
|
|
||||||
|
// Internal function
|
||||||
|
int sc_log_log(enum sc_log_level level, const char *fmt, ...);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Max file size to rotate.
|
||||||
|
*/
|
||||||
|
#define SC_LOG_FILE_SIZE (2 * 1024 * 1024)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define SC_LOG_PRINT_FILE_NAME if you want to print file name and line number
|
||||||
|
* in the log line.
|
||||||
|
*/
|
||||||
|
#ifdef SC_LOG_PRINT_FILE_NAME
|
||||||
|
#define sc_log_ap(fmt, ...) \
|
||||||
|
"(%s:%d) " fmt "\n", strrchr("/" __FILE__, '/') + 1, __LINE__, \
|
||||||
|
__VA_ARGS__
|
||||||
|
#else
|
||||||
|
#define sc_log_ap(fmt, ...) fmt "\n", __VA_ARGS__
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Printf-style format
|
||||||
|
* e.g
|
||||||
|
* sc_log_error("Errno : %d, reason : %s", errno, strerror(errno));
|
||||||
|
*/
|
||||||
|
#define sc_log_debug(...) (sc_log_log(SC_LOG_DEBUG, sc_log_ap(__VA_ARGS__, "")))
|
||||||
|
#define sc_log_info(...) (sc_log_log(SC_LOG_INFO, sc_log_ap(__VA_ARGS__, "")))
|
||||||
|
#define sc_log_warn(...) (sc_log_log(SC_LOG_WARN, sc_log_ap(__VA_ARGS__, "")))
|
||||||
|
#define sc_log_error(...) (sc_log_log(SC_LOG_ERROR, sc_log_ap(__VA_ARGS__, "")))
|
||||||
|
|
||||||
|
#endif
|
103
map/CMakeLists.txt
Normal file
103
map/CMakeLists.txt
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.5.1)
|
||||||
|
project(sc_map C)
|
||||||
|
|
||||||
|
set(CMAKE_C_STANDARD 99)
|
||||||
|
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||||
|
set(CMAKE_C_EXTENSIONS OFF)
|
||||||
|
|
||||||
|
add_executable(sc_map map_example.c sc_map.h sc_map.c)
|
||||||
|
|
||||||
|
if (NOT CMAKE_C_COMPILER_ID MATCHES "MSVC")
|
||||||
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -Wall -pedantic -Werror -D_GNU_SOURCE")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# --------------------- Test Configuration Start ---------------------------- #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
|
||||||
|
include(CTest)
|
||||||
|
include(CheckCCompilerFlag)
|
||||||
|
|
||||||
|
enable_testing()
|
||||||
|
|
||||||
|
add_executable(${PROJECT_NAME}_test map_test.c sc_map.c)
|
||||||
|
|
||||||
|
target_compile_options(${PROJECT_NAME}_test PRIVATE -DSC_SIZE_MAX=140000ul)
|
||||||
|
|
||||||
|
if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||||
|
if ("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR
|
||||||
|
"${CMAKE_C_COMPILER_ID}" STREQUAL "GNU")
|
||||||
|
|
||||||
|
target_compile_options(${PROJECT_NAME}_test PRIVATE -DSC_HAVE_WRAP)
|
||||||
|
target_compile_options(${PROJECT_NAME}_test PRIVATE -fno-builtin)
|
||||||
|
target_link_options(${PROJECT_NAME}_test PRIVATE -Wl,--wrap=calloc)
|
||||||
|
|
||||||
|
endif ()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
if ("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR
|
||||||
|
"${CMAKE_C_COMPILER_ID}" STREQUAL "AppleClang" OR
|
||||||
|
"${CMAKE_C_COMPILER_ID}" STREQUAL "GNU")
|
||||||
|
|
||||||
|
target_compile_options(${PROJECT_NAME}_test PRIVATE -fno-omit-frame-pointer)
|
||||||
|
|
||||||
|
if (SANITIZER)
|
||||||
|
target_compile_options(${PROJECT_NAME}_test PRIVATE -fsanitize=${SANITIZER})
|
||||||
|
target_link_options(${PROJECT_NAME}_test PRIVATE -fsanitize=${SANITIZER})
|
||||||
|
endif ()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
add_test(NAME ${PROJECT_NAME}_test COMMAND ${PROJECT_NAME}_test)
|
||||||
|
|
||||||
|
SET(MEMORYCHECK_COMMAND_OPTIONS
|
||||||
|
"-q --log-fd=2 --trace-children=yes --track-origins=yes \
|
||||||
|
--leak-check=full --show-leak-kinds=all --show-reachable=yes \
|
||||||
|
--error-exitcode=255")
|
||||||
|
|
||||||
|
add_custom_target(valgrind_${PROJECT_NAME} ${CMAKE_COMMAND}
|
||||||
|
-E env CTEST_OUTPUT_ON_FAILURE=1
|
||||||
|
${CMAKE_CTEST_COMMAND} -C $<CONFIG>
|
||||||
|
--overwrite MemoryCheckCommandOptions=${MEMORYCHECK_COMMAND_OPTIONS}
|
||||||
|
--verbose -T memcheck WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||||
|
|
||||||
|
add_custom_target(check_${PROJECT_NAME} ${CMAKE_COMMAND}
|
||||||
|
-E env CTEST_OUTPUT_ON_FAILURE=1
|
||||||
|
${CMAKE_CTEST_COMMAND} -C $<CONFIG> --verbose
|
||||||
|
WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||||
|
|
||||||
|
# ----------------------- - Code Coverage Start ----------------------------- #
|
||||||
|
|
||||||
|
if (${CMAKE_BUILD_TYPE} MATCHES "Coverage")
|
||||||
|
if ("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang")
|
||||||
|
|
||||||
|
target_compile_options(${PROJECT_NAME}_test PRIVATE -fprofile-instr-generate)
|
||||||
|
target_compile_options(${PROJECT_NAME}_test PRIVATE -fcoverage-mapping)
|
||||||
|
target_link_options(${PROJECT_NAME}_test PRIVATE -fprofile-instr-generate)
|
||||||
|
target_link_libraries(${PROJECT_NAME}_test PRIVATE clang_rt.profile-x86_64)
|
||||||
|
|
||||||
|
elseif ("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU")
|
||||||
|
|
||||||
|
target_compile_options(${PROJECT_NAME}_test PRIVATE --coverage)
|
||||||
|
target_link_libraries(${PROJECT_NAME}_test gcov)
|
||||||
|
|
||||||
|
endif ()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
add_custom_target(coverage_${PROJECT_NAME})
|
||||||
|
add_custom_command(
|
||||||
|
TARGET coverage_${PROJECT_NAME}
|
||||||
|
COMMAND lcov --capture --directory ..
|
||||||
|
--output-file coverage.info --rc lcov_branch_coverage=1
|
||||||
|
COMMAND lcov --remove coverage.info '/usr/*' '*example*' '*test*'
|
||||||
|
--output-file coverage.info --rc lcov_branch_coverage=1
|
||||||
|
COMMAND lcov --list coverage.info --rc lcov_branch_coverage=1
|
||||||
|
)
|
||||||
|
|
||||||
|
add_dependencies(coverage_${PROJECT_NAME} check_${PROJECT_NAME})
|
||||||
|
|
||||||
|
# -------------------------- Code Coverage End ------------------------------ #
|
||||||
|
|
||||||
|
|
||||||
|
# ----------------------- Test Configuration End ---------------------------- #
|
||||||
|
|
54
map/README.md
Normal file
54
map/README.md
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
# Hashmap
|
||||||
|
|
||||||
|
### Overview
|
||||||
|
|
||||||
|
- Open addressing hashmap with linear probing.
|
||||||
|
- Just copy <b>sc_map.h</b> and <b>sc_map.c</b> to your project.
|
||||||
|
|
||||||
|
#### Usage
|
||||||
|
|
||||||
|
|
||||||
|
```c
|
||||||
|
#include "sc_map.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
char *key, *value;
|
||||||
|
struct sc_map_str map;
|
||||||
|
|
||||||
|
sc_map_init_str(&map, 0, 0);
|
||||||
|
|
||||||
|
sc_map_put_str(&map, "jack", "chicago");
|
||||||
|
sc_map_put_str(&map, "jane", "new york");
|
||||||
|
sc_map_put_str(&map, "janie", "atlanta");
|
||||||
|
|
||||||
|
sc_map_foreach (&map, key, value) {
|
||||||
|
printf("Key:[%s], Value:[%s] \n", key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_map_term_str(&map);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
####Internals
|
||||||
|
##### Memory
|
||||||
|
- Entries are kept in a single array without additional bookkeeping data.
|
||||||
|
|
||||||
|
- Single allocation for all the data. Underlying array size is always power
|
||||||
|
of two, so there are some overhead for allocated but yet not used entries.
|
||||||
|
e.g (If you have 9 entries, underlying array has capacity for 16 entries)
|
||||||
|
|
||||||
|
##### Performance
|
||||||
|
- Hashmap is basically a map of 'uint64_t' keys to 'void*' values.
|
||||||
|
|
||||||
|
- An entry is 16 bytes (64 bit systems), as this is an open addressing hashmap,
|
||||||
|
linear probing with 16 bytes entries plays nicely with cache lines and
|
||||||
|
hardware prefetcher.
|
||||||
|
|
||||||
|
- It is intentionally not a 'generic' structure. Storing small key value pairs
|
||||||
|
provides really good performance.
|
23
map/map_example.c
Normal file
23
map/map_example.c
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
#include "sc_map.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
char *key, *value;
|
||||||
|
struct sc_map_str map;
|
||||||
|
|
||||||
|
sc_map_init_str(&map, 0, 0);
|
||||||
|
|
||||||
|
sc_map_put_str(&map, "jack", "chicago");
|
||||||
|
sc_map_put_str(&map, "jane", "new york");
|
||||||
|
sc_map_put_str(&map, "janie", "atlanta");
|
||||||
|
|
||||||
|
sc_map_foreach (&map, key, value) {
|
||||||
|
printf("Key:[%s], Value:[%s] \n", key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_map_term_str(&map);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
469
map/map_test.c
Normal file
469
map/map_test.c
Normal file
@ -0,0 +1,469 @@
|
|||||||
|
#include "sc_map.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
void example(void)
|
||||||
|
{
|
||||||
|
char *key, *value;
|
||||||
|
struct sc_map_str map;
|
||||||
|
|
||||||
|
sc_map_init_str(&map, 0, 0);
|
||||||
|
|
||||||
|
sc_map_put_str(&map, "jack", "chicago");
|
||||||
|
sc_map_put_str(&map, "jane", "new york");
|
||||||
|
sc_map_put_str(&map, "janie", "atlanta");
|
||||||
|
|
||||||
|
sc_map_foreach (&map, key, value) {
|
||||||
|
printf("Key:[%s], Value:[%s] \n", key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_map_term_str(&map);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *str_random(size_t size)
|
||||||
|
{
|
||||||
|
static char ch[] = "0123456789"
|
||||||
|
"abcdefghijklmnopqrstuvwxyz"
|
||||||
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||||
|
uint32_t index;
|
||||||
|
char *dest = malloc(size + 1);
|
||||||
|
|
||||||
|
for (int i = 0; i < size; ++i) {
|
||||||
|
index = (uint32_t)((double) rand() / RAND_MAX * (sizeof(ch) - 1));
|
||||||
|
dest[i] = ch[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
dest[size - 1] = '\0';
|
||||||
|
|
||||||
|
return dest;
|
||||||
|
}
|
||||||
|
|
||||||
|
void test1()
|
||||||
|
{
|
||||||
|
struct sc_map_str map;
|
||||||
|
char *keys[128];
|
||||||
|
char *values[128];
|
||||||
|
char *key, *value;
|
||||||
|
|
||||||
|
for (int i = 0; i < 128; i++) {
|
||||||
|
keys[i] = str_random((rand() % 64) + 32);
|
||||||
|
values[i] = str_random((rand() % 64) + 32);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(!sc_map_init_str(&map, 0, -1));
|
||||||
|
assert(!sc_map_init_str(&map, 0, 24));
|
||||||
|
assert(!sc_map_init_str(&map, 0, 96));
|
||||||
|
assert(sc_map_init_str(&map, 0, 0));
|
||||||
|
assert(sc_map_size_str(&map) == 0);
|
||||||
|
sc_map_clear_str(&map);
|
||||||
|
assert(sc_map_size_str(&map) == 0);
|
||||||
|
sc_map_term_str(&map);
|
||||||
|
assert(sc_map_init_str(&map, 0, 0));
|
||||||
|
|
||||||
|
sc_map_foreach (&map, key, value) {
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_map_foreach_key (&map, key) {
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_map_foreach_value (&map, value) {
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(sc_map_put_str(&map, "key", "value"));
|
||||||
|
assert(sc_map_put_str(&map, "key", "value2"));
|
||||||
|
assert(sc_map_get_str(&map, "key", &value));
|
||||||
|
assert(strcmp(value, "value2") == 0);
|
||||||
|
|
||||||
|
assert(sc_map_del_str(&map, "key", NULL));
|
||||||
|
assert(!sc_map_get_str(&map, "key", &value));
|
||||||
|
assert(sc_map_put_str(&map, "key", "value3"));
|
||||||
|
assert(sc_map_del_str(&map, "key", &value));
|
||||||
|
assert(strcmp(value, "value3") == 0);
|
||||||
|
assert(!sc_map_del_str(&map, "key", &value));
|
||||||
|
|
||||||
|
assert(sc_map_put_str(&map, "key", "value"));
|
||||||
|
assert(sc_map_size_str(&map) == 1);
|
||||||
|
assert(sc_map_put_str(&map, NULL, "nullvalue"));
|
||||||
|
assert(sc_map_size_str(&map) == 2);
|
||||||
|
assert(sc_map_get_str(&map, NULL, &value));
|
||||||
|
assert(strcmp(value, "nullvalue") == 0);
|
||||||
|
assert(sc_map_del_str(&map, NULL, NULL));
|
||||||
|
assert(sc_map_size_str(&map) == 1);
|
||||||
|
|
||||||
|
sc_map_clear_str(&map);
|
||||||
|
assert(sc_map_size_str(&map) == 0);
|
||||||
|
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
assert(sc_map_put_str(&map, keys[i], values[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
assert(sc_map_get_str(&map, keys[i], &value));
|
||||||
|
assert(strcmp(value, values[i]) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_map_put_str(&map, keys[0], values[101]);
|
||||||
|
assert(sc_map_size_str(&map) == 100);
|
||||||
|
sc_map_put_str(&map, keys[101], values[102]);
|
||||||
|
assert(sc_map_size_str(&map) == 101);
|
||||||
|
sc_map_clear_str(&map);
|
||||||
|
assert(sc_map_size_str(&map) == 0);
|
||||||
|
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
assert(sc_map_put_str(&map, keys[i], values[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
assert(sc_map_get_str(&map, keys[i], &value));
|
||||||
|
assert(strcmp(value, values[i]) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_map_term_str(&map);
|
||||||
|
|
||||||
|
assert(sc_map_init_str(&map, 0, 0));
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
assert(sc_map_put_str(&map, keys[i], values[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool found;
|
||||||
|
sc_map_foreach (&map, key, value) {
|
||||||
|
found = false;
|
||||||
|
for (int j = 0; j < 100; j++) {
|
||||||
|
if (strcmp(key, keys[j]) == 0 && strcmp(value, values[j]) == 0) {
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert(found);
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_map_foreach_key (&map, key) {
|
||||||
|
found = false;
|
||||||
|
for (int j = 0; j < 100; j++) {
|
||||||
|
if (strcmp(key, keys[j]) == 0) {
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert(found);
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_map_foreach_value (&map, value) {
|
||||||
|
found = false;
|
||||||
|
for (int j = 0; j < 100; j++) {
|
||||||
|
if (strcmp(value, values[j]) == 0) {
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert(found);
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_map_term_str(&map);
|
||||||
|
for (int i = 0; i < 128; i++) {
|
||||||
|
free(keys[i]);
|
||||||
|
free(values[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void test2()
|
||||||
|
{
|
||||||
|
struct sc_map_32 map;
|
||||||
|
uint32_t keys[128];
|
||||||
|
uint32_t values[128];
|
||||||
|
uint32_t key, value;
|
||||||
|
int random;
|
||||||
|
|
||||||
|
for (int i = 0; i < 128; i++) {
|
||||||
|
retry:
|
||||||
|
random = rand();
|
||||||
|
for (int j = 0; j < i; j++) {
|
||||||
|
if (keys[j] == random) {
|
||||||
|
goto retry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
keys[i] = random;
|
||||||
|
values[i] = rand();
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(sc_map_init_32(&map, 16, 50));
|
||||||
|
assert(sc_map_size_32(&map) == 0);
|
||||||
|
assert(sc_map_put_32(&map, 0, 0));
|
||||||
|
sc_map_clear_32(&map);
|
||||||
|
assert(sc_map_size_32(&map) == 0);
|
||||||
|
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
assert(sc_map_put_32(&map, keys[i], values[i]));
|
||||||
|
assert(sc_map_get_32(&map, keys[i], &value));
|
||||||
|
assert(value == values[i]);
|
||||||
|
assert(sc_map_put_32(&map, keys[i], values[i]));
|
||||||
|
assert(sc_map_del_32(&map, keys[i], &value));
|
||||||
|
assert(value == values[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < 128; i++) {
|
||||||
|
assert(sc_map_put_32(&map, keys[i], values[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(sc_map_size_32(&map) == 128);
|
||||||
|
|
||||||
|
bool found;
|
||||||
|
sc_map_foreach (&map, key, value) {
|
||||||
|
found = false;
|
||||||
|
for (int j = 0; j < 128; j++) {
|
||||||
|
if (key == keys[j] && value == values[j]) {
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert(found);
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_map_foreach_key (&map, key) {
|
||||||
|
found = false;
|
||||||
|
for (int j = 0; j < 128; j++) {
|
||||||
|
if (key == keys[j]) {
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert(found);
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_map_foreach_value (&map, value) {
|
||||||
|
found = false;
|
||||||
|
for (int j = 0; j < 128; j++) {
|
||||||
|
if (value == values[j]) {
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert(found);
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_map_term_32(&map);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test3()
|
||||||
|
{
|
||||||
|
struct sc_map_64 map;
|
||||||
|
uint64_t keys[128];
|
||||||
|
uint64_t values[128];
|
||||||
|
uint64_t key, value;
|
||||||
|
int random;
|
||||||
|
|
||||||
|
for (int i = 0; i < 128; i++) {
|
||||||
|
retry:
|
||||||
|
random = rand();
|
||||||
|
for (int j = 0; j < i; j++) {
|
||||||
|
if (keys[j] == random) {
|
||||||
|
goto retry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
keys[i] = random;
|
||||||
|
values[i] = rand();
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(sc_map_init_64(&map, 16, 50));
|
||||||
|
assert(sc_map_size_64(&map) == 0);
|
||||||
|
assert(sc_map_put_64(&map, 0, 0));
|
||||||
|
sc_map_clear_64(&map);
|
||||||
|
assert(sc_map_size_64(&map) == 0);
|
||||||
|
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
assert(sc_map_put_64(&map, keys[i], values[i]));
|
||||||
|
assert(sc_map_get_64(&map, keys[i], &value));
|
||||||
|
assert(value == values[i]);
|
||||||
|
assert(sc_map_put_64(&map, keys[i], values[i]));
|
||||||
|
assert(sc_map_del_64(&map, keys[i], &value));
|
||||||
|
assert(value == values[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < 128; i++) {
|
||||||
|
assert(sc_map_put_64(&map, keys[i], values[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(sc_map_size_64(&map) == 128);
|
||||||
|
|
||||||
|
bool found;
|
||||||
|
sc_map_foreach (&map, key, value) {
|
||||||
|
found = false;
|
||||||
|
for (int j = 0; j < 128; j++) {
|
||||||
|
if (key == keys[j] && value == values[j]) {
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert(found);
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_map_foreach_key (&map, key) {
|
||||||
|
found = false;
|
||||||
|
for (int j = 0; j < 128; j++) {
|
||||||
|
if (key == keys[j]) {
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert(found);
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_map_foreach_value (&map, value) {
|
||||||
|
found = false;
|
||||||
|
for (int j = 0; j < 128; j++) {
|
||||||
|
if (value == values[j]) {
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert(found);
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_map_term_64(&map);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test4()
|
||||||
|
{
|
||||||
|
char* c;
|
||||||
|
struct sc_map_64s map64s;
|
||||||
|
|
||||||
|
assert(sc_map_init_64s(&map64s, 0, 87));
|
||||||
|
for (int i = 0 ; i < 100; i++) {
|
||||||
|
assert(sc_map_put_64s(&map64s, i, NULL));
|
||||||
|
assert(sc_map_get_64s(&map64s, i, &c));
|
||||||
|
assert(c == NULL);
|
||||||
|
}
|
||||||
|
assert(sc_map_size_64s(&map64s) == 100);
|
||||||
|
for (int i = 0 ; i < 100; i++) {
|
||||||
|
assert(sc_map_del_64s(&map64s, i, &c));
|
||||||
|
assert(c == NULL);
|
||||||
|
}
|
||||||
|
assert(sc_map_size_64s(&map64s) == 0);
|
||||||
|
assert(sc_map_put_64s(&map64s, 3, NULL));
|
||||||
|
assert(sc_map_size_64s(&map64s) == 1);
|
||||||
|
sc_map_clear_64s(&map64s);
|
||||||
|
assert(sc_map_size_64s(&map64s) == 0);
|
||||||
|
|
||||||
|
sc_map_term_64s(&map64s);
|
||||||
|
|
||||||
|
void* v;
|
||||||
|
struct sc_map_64v map64v;
|
||||||
|
|
||||||
|
assert(sc_map_init_64v(&map64v, 0, 87));
|
||||||
|
for (int i = 0 ; i < 100; i++) {
|
||||||
|
assert(sc_map_put_64v(&map64v, i, NULL));
|
||||||
|
assert(sc_map_get_64v(&map64v, i, &v));
|
||||||
|
assert(c == NULL);
|
||||||
|
}
|
||||||
|
assert(sc_map_size_64v(&map64v) == 100);
|
||||||
|
for (int i = 0 ; i < 100; i++) {
|
||||||
|
assert(sc_map_del_64v(&map64v, i, &v));
|
||||||
|
assert(v == NULL);
|
||||||
|
}
|
||||||
|
assert(sc_map_size_64v(&map64v) == 0);
|
||||||
|
assert(sc_map_put_64v(&map64v, 3, NULL));
|
||||||
|
assert(sc_map_size_64v(&map64v) == 1);
|
||||||
|
sc_map_clear_64v(&map64v);
|
||||||
|
assert(sc_map_size_64v(&map64v) == 0);
|
||||||
|
|
||||||
|
sc_map_term_64v(&map64v);
|
||||||
|
|
||||||
|
struct sc_map_sv mapsv;
|
||||||
|
|
||||||
|
assert(sc_map_init_sv(&mapsv, 0, 87));
|
||||||
|
for (int i = 0 ; i < 100; i++) {
|
||||||
|
assert(sc_map_put_sv(&mapsv, "", NULL));
|
||||||
|
assert(sc_map_get_sv(&mapsv, "", &v));
|
||||||
|
assert(v == NULL);
|
||||||
|
}
|
||||||
|
assert(sc_map_size_sv(&mapsv) == 1);
|
||||||
|
assert(sc_map_del_sv(&mapsv, "", &v));
|
||||||
|
assert(v == NULL);
|
||||||
|
assert(sc_map_size_sv(&mapsv) == 0);
|
||||||
|
assert(!sc_map_del_sv(&mapsv, "", &v));
|
||||||
|
sc_map_clear_sv(&mapsv);
|
||||||
|
assert(sc_map_size_sv(&mapsv) == 0);
|
||||||
|
sc_map_term_sv(&mapsv);
|
||||||
|
|
||||||
|
uint64_t val;
|
||||||
|
struct sc_map_s64 maps64;
|
||||||
|
|
||||||
|
assert(sc_map_init_s64(&maps64, 0, 26));
|
||||||
|
assert(sc_map_put_s64(&maps64, "", 511));
|
||||||
|
assert(sc_map_put_s64(&maps64, "", 511));
|
||||||
|
assert(sc_map_get_s64(&maps64, "", &val));
|
||||||
|
assert(val == 511);
|
||||||
|
assert(sc_map_size_s64(&maps64) == 1);
|
||||||
|
assert(sc_map_del_s64(&maps64, "", &val));
|
||||||
|
assert(val == 511);
|
||||||
|
assert(sc_map_size_s64(&maps64) == 0);
|
||||||
|
sc_map_clear_s64(&maps64);
|
||||||
|
sc_map_term_s64(&maps64);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef SC_HAVE_WRAP
|
||||||
|
|
||||||
|
bool fail_calloc = false;
|
||||||
|
void *__real_calloc(size_t n, size_t size);
|
||||||
|
void *__wrap_calloc(size_t n, size_t size)
|
||||||
|
{
|
||||||
|
if (fail_calloc) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return __real_calloc(n, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void fail_test()
|
||||||
|
{
|
||||||
|
struct sc_map_32 map;
|
||||||
|
|
||||||
|
fail_calloc = true;
|
||||||
|
assert(!sc_map_init_32(&map, 10, 0));
|
||||||
|
fail_calloc = false;
|
||||||
|
assert(sc_map_init_32(&map, 10, 0));
|
||||||
|
|
||||||
|
fail_calloc = true;
|
||||||
|
bool success = true;
|
||||||
|
for (int i =0 ; i < 20; i++) {
|
||||||
|
success = sc_map_put_32(&map, i, i);
|
||||||
|
}
|
||||||
|
assert(!success);
|
||||||
|
fail_calloc = false;
|
||||||
|
assert(sc_map_put_32(&map, 44444, 44444));
|
||||||
|
|
||||||
|
for (int i = 0 ; i < SC_SIZE_MAX; i++) {
|
||||||
|
success = sc_map_put_32(&map, i, i);
|
||||||
|
}
|
||||||
|
assert(!success);
|
||||||
|
fail_calloc = false;
|
||||||
|
|
||||||
|
sc_map_term_32(&map);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
void fail_test(void)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
example();
|
||||||
|
fail_test();
|
||||||
|
test1();
|
||||||
|
test2();
|
||||||
|
test3();
|
||||||
|
test4();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
356
map/sc_map.c
Normal file
356
map/sc_map.c
Normal file
@ -0,0 +1,356 @@
|
|||||||
|
#include "sc_map.h"
|
||||||
|
|
||||||
|
#include <memory.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#ifndef SC_SIZE_MAX
|
||||||
|
#define SC_SIZE_MAX UINT32_MAX
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define sc_map_impl_of_strkey(name, K, V, cmp, hash_fn) \
|
||||||
|
bool sc_map_cmp_##name(struct sc_map_item_##name *t, K key, uint32_t hash) \
|
||||||
|
{ \
|
||||||
|
return t->hash == hash && cmp(t->key, key); \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
void sc_map_assign_##name(struct sc_map_item_##name *t, K key, V value, \
|
||||||
|
uint32_t hash) \
|
||||||
|
{ \
|
||||||
|
t->key = key; \
|
||||||
|
t->value = value; \
|
||||||
|
t->hash = hash; \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
uint32_t sc_map_hashof_##name(struct sc_map_item_##name *t) \
|
||||||
|
{ \
|
||||||
|
return t->hash; \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
sc_map_impl_of(name, K, V, cmp, hash_fn)
|
||||||
|
|
||||||
|
#define sc_map_impl_of_scalar(name, K, V, cmp, hash_fn) \
|
||||||
|
bool sc_map_cmp_##name(struct sc_map_item_##name *t, K key, uint32_t hash) \
|
||||||
|
{ \
|
||||||
|
return cmp(t->key, key); \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
void sc_map_assign_##name(struct sc_map_item_##name *t, K key, V value, \
|
||||||
|
uint32_t hash) \
|
||||||
|
{ \
|
||||||
|
t->key = key; \
|
||||||
|
t->value = value; \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
uint32_t sc_map_hashof_##name(struct sc_map_item_##name *t) \
|
||||||
|
{ \
|
||||||
|
return hash_fn(t->key); \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
sc_map_impl_of(name, K, V, cmp, hash_fn)
|
||||||
|
|
||||||
|
#define sc_map_impl_of(name, K, V, cmp, hash_fn) \
|
||||||
|
\
|
||||||
|
static const struct sc_map_##name sc_map_empty_##name = { \
|
||||||
|
.cap = 1, \
|
||||||
|
.mem = &(struct sc_map_item_##name){.key = (0)}}; \
|
||||||
|
\
|
||||||
|
static void *sc_map_alloc_##name(uint32_t *cap, uint32_t factor) \
|
||||||
|
{ \
|
||||||
|
uint32_t v = *cap; \
|
||||||
|
struct sc_map_item_##name *t; \
|
||||||
|
\
|
||||||
|
if (*cap > SC_SIZE_MAX / factor) { \
|
||||||
|
return NULL; \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
/* Find next power of two */ \
|
||||||
|
v = v < 8 ? 8 : (v * factor); \
|
||||||
|
v--; \
|
||||||
|
for (uint32_t i = 1; i < sizeof(v) * 8; i *= 2) { \
|
||||||
|
v |= v >> i; \
|
||||||
|
} \
|
||||||
|
v++; \
|
||||||
|
\
|
||||||
|
*cap = v; \
|
||||||
|
return sc_map_calloc(sizeof(*t), v); \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
bool sc_map_init_##name(struct sc_map_##name *map, uint32_t cap, \
|
||||||
|
uint32_t load_factor) \
|
||||||
|
{ \
|
||||||
|
void *t; \
|
||||||
|
uint32_t f = (load_factor == 0) ? 75 : load_factor; \
|
||||||
|
\
|
||||||
|
if (f > 95 || f < 25) { \
|
||||||
|
return false; \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
if (cap == 0) { \
|
||||||
|
*map = sc_map_empty_##name; \
|
||||||
|
map->load_factor = f; \
|
||||||
|
return true; \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
t = sc_map_alloc_##name(&cap, 1); \
|
||||||
|
if (t == NULL) { \
|
||||||
|
return false; \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
map->mem = t; \
|
||||||
|
map->size = 0; \
|
||||||
|
map->used = false; \
|
||||||
|
map->cap = cap; \
|
||||||
|
map->load_factor = f; \
|
||||||
|
map->remap = (uint32_t)(map->cap * ((double) map->load_factor / 100)); \
|
||||||
|
\
|
||||||
|
return true; \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
void sc_map_term_##name(struct sc_map_##name *map) \
|
||||||
|
{ \
|
||||||
|
if (map->mem != sc_map_empty_##name.mem) { \
|
||||||
|
sc_map_free(map->mem); \
|
||||||
|
} \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
uint32_t sc_map_size_##name(struct sc_map_##name *map) \
|
||||||
|
{ \
|
||||||
|
return map->size; \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
void sc_map_clear_##name(struct sc_map_##name *map) \
|
||||||
|
{ \
|
||||||
|
if (map->size > 0) { \
|
||||||
|
for (uint32_t i = 0; i < map->cap; i++) { \
|
||||||
|
map->mem[i].key = 0; \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
map->size = 0; \
|
||||||
|
} \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
static bool sc_map_remap_##name(struct sc_map_##name *map) \
|
||||||
|
{ \
|
||||||
|
uint32_t pos, cap, mod; \
|
||||||
|
struct sc_map_item_##name *new; \
|
||||||
|
\
|
||||||
|
if (map->size < map->remap) { \
|
||||||
|
return true; \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
cap = map->cap; \
|
||||||
|
new = sc_map_alloc_##name(&cap, 2); \
|
||||||
|
if (new == NULL) { \
|
||||||
|
return false; \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
mod = cap - 1; \
|
||||||
|
\
|
||||||
|
for (uint32_t i = 0; i < map->cap; i++) { \
|
||||||
|
if (map->mem[i].key != 0) { \
|
||||||
|
pos = sc_map_hashof_##name(&map->mem[i]) & (mod); \
|
||||||
|
\
|
||||||
|
while (true) { \
|
||||||
|
if (new[pos].key == 0) { \
|
||||||
|
new[pos] = map->mem[i]; \
|
||||||
|
break; \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
pos = (pos + 1) & (mod); \
|
||||||
|
} \
|
||||||
|
} \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
if (map->mem != sc_map_empty_##name.mem) { \
|
||||||
|
sc_map_free(map->mem); \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
map->mem = new; \
|
||||||
|
map->cap = cap; \
|
||||||
|
map->remap = (uint32_t)(map->cap * ((double) map->load_factor / 100)); \
|
||||||
|
\
|
||||||
|
return true; \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
bool sc_map_put_##name(struct sc_map_##name *map, K key, V value) \
|
||||||
|
{ \
|
||||||
|
uint32_t pos, mod, hash; \
|
||||||
|
\
|
||||||
|
if (key == 0) { \
|
||||||
|
map->size += !map->used; \
|
||||||
|
map->used = 1; \
|
||||||
|
map->value = value; \
|
||||||
|
\
|
||||||
|
return true; \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
if (!sc_map_remap_##name(map)) { \
|
||||||
|
return false; \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
mod = map->cap - 1; \
|
||||||
|
hash = hash_fn(key); \
|
||||||
|
pos = hash & (mod); \
|
||||||
|
\
|
||||||
|
while (true) { \
|
||||||
|
if (map->mem[pos].key == 0) { \
|
||||||
|
map->size++; \
|
||||||
|
} else if (sc_map_cmp_##name(&map->mem[pos], key, hash) != true) { \
|
||||||
|
pos = (pos + 1) & (mod); \
|
||||||
|
continue; \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
sc_map_assign_##name(&map->mem[pos], key, value, hash); \
|
||||||
|
return true; \
|
||||||
|
} \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
bool sc_map_get_##name(struct sc_map_##name *map, K key, V *value) \
|
||||||
|
{ \
|
||||||
|
const uint32_t mod = map->cap - 1; \
|
||||||
|
uint32_t hash, pos; \
|
||||||
|
\
|
||||||
|
if (key == 0) { \
|
||||||
|
*value = map->value; \
|
||||||
|
return map->used; \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
hash = hash_fn(key); \
|
||||||
|
pos = hash & mod; \
|
||||||
|
\
|
||||||
|
while (true) { \
|
||||||
|
if (map->mem[pos].key == 0) { \
|
||||||
|
return false; \
|
||||||
|
} else if (sc_map_cmp_##name(&map->mem[pos], key, hash) != true) { \
|
||||||
|
pos = (pos + 1) & (mod); \
|
||||||
|
continue; \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
*value = map->mem[pos].value; \
|
||||||
|
return true; \
|
||||||
|
} \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
bool sc_map_del_##name(struct sc_map_##name *map, K key, V *value) \
|
||||||
|
{ \
|
||||||
|
const uint32_t mod = map->cap - 1; \
|
||||||
|
uint32_t pos, prev_elem, curr, curr_orig, hash; \
|
||||||
|
\
|
||||||
|
if (key == 0) { \
|
||||||
|
bool ret = map->used; \
|
||||||
|
map->size -= map->used; \
|
||||||
|
map->used = false; \
|
||||||
|
\
|
||||||
|
if (value != NULL) { \
|
||||||
|
*value = map->value; \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
return ret; \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
hash = hash_fn(key); \
|
||||||
|
pos = hash & (mod); \
|
||||||
|
\
|
||||||
|
while (true) { \
|
||||||
|
if (map->mem[pos].key == 0) { \
|
||||||
|
return false; \
|
||||||
|
} else if (sc_map_cmp_##name(&map->mem[pos], key, hash) != true) { \
|
||||||
|
pos = (pos + 1) & (mod); \
|
||||||
|
continue; \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
if (value != NULL) { \
|
||||||
|
*value = map->mem[pos].value; \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
map->size--; \
|
||||||
|
map->mem[pos].key = 0; \
|
||||||
|
prev_elem = pos; \
|
||||||
|
curr = pos; \
|
||||||
|
\
|
||||||
|
while (true) { \
|
||||||
|
curr = (curr + 1) & (mod); \
|
||||||
|
if (map->mem[curr].key == 0) { \
|
||||||
|
break; \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
curr_orig = sc_map_hashof_##name(&map->mem[curr]) & (mod); \
|
||||||
|
\
|
||||||
|
if ((curr_orig > curr && \
|
||||||
|
(curr_orig <= prev_elem || curr >= prev_elem)) || \
|
||||||
|
(curr_orig <= prev_elem && curr >= prev_elem)) { \
|
||||||
|
\
|
||||||
|
map->mem[prev_elem] = map->mem[curr]; \
|
||||||
|
map->mem[curr].key = 0; \
|
||||||
|
prev_elem = curr; \
|
||||||
|
} \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
return true; \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static uint32_t sc_map_hash_32(uint32_t a)
|
||||||
|
{
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t sc_map_hash_64(uint64_t a)
|
||||||
|
{
|
||||||
|
return ((uint32_t) a) ^ (uint32_t)(a >> 32u);
|
||||||
|
}
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
uint32_t murmurhash(const char *key)
|
||||||
|
{
|
||||||
|
const uint64_t m = UINT64_C(0xc6a4a7935bd1e995);
|
||||||
|
const size_t len = strlen(key);
|
||||||
|
const char *end = key + (len & ~(uint64_t) 0x7);
|
||||||
|
uint64_t h = (len * m);
|
||||||
|
|
||||||
|
while (key != end) {
|
||||||
|
uint64_t k;
|
||||||
|
memcpy(&k, key, sizeof(k));
|
||||||
|
|
||||||
|
k *= m;
|
||||||
|
k ^= k >> 47u;
|
||||||
|
k *= m;
|
||||||
|
|
||||||
|
h ^= k;
|
||||||
|
h *= m;
|
||||||
|
key += 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (len & 7u) {
|
||||||
|
case 7: h ^= (uint64_t) key[6] << 48ul;
|
||||||
|
case 6: h ^= (uint64_t) key[5] << 40ul;
|
||||||
|
case 5: h ^= (uint64_t) key[4] << 32ul;
|
||||||
|
case 4: h ^= (uint64_t) key[3] << 24ul;
|
||||||
|
case 3: h ^= (uint64_t) key[2] << 16ul;
|
||||||
|
case 2: h ^= (uint64_t) key[1] << 8ul;
|
||||||
|
case 1: h ^= (uint64_t) key[0];
|
||||||
|
h *= m;
|
||||||
|
};
|
||||||
|
|
||||||
|
h ^= h >> 47u;
|
||||||
|
h *= m;
|
||||||
|
h ^= h >> 47u;
|
||||||
|
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
// clang-format off
|
||||||
|
|
||||||
|
#define sc_map_varcmp(a, b) ((a) == (b))
|
||||||
|
#define sc_map_strcmp(a, b) (!strcmp(a, b))
|
||||||
|
|
||||||
|
// name, key type, value type, cmp hash
|
||||||
|
sc_map_impl_of_scalar(32, uint32_t, uint32_t, sc_map_varcmp, sc_map_hash_32)
|
||||||
|
sc_map_impl_of_scalar(64, uint64_t, uint64_t, sc_map_varcmp, sc_map_hash_64)
|
||||||
|
sc_map_impl_of_scalar(64v, uint64_t, void *, sc_map_varcmp, sc_map_hash_64)
|
||||||
|
sc_map_impl_of_scalar(64s, uint64_t, char *, sc_map_varcmp, sc_map_hash_64)
|
||||||
|
sc_map_impl_of_strkey(str, char *, char *, sc_map_strcmp, murmurhash)
|
||||||
|
sc_map_impl_of_strkey(sv, char *, void *, sc_map_strcmp, murmurhash)
|
||||||
|
sc_map_impl_of_strkey(s64, char *, uint64_t, sc_map_strcmp, murmurhash)
|
||||||
|
|
||||||
|
// clang-format on
|
102
map/sc_map.h
Normal file
102
map/sc_map.h
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020 Ozan Tezcan
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef SC_MAP_H
|
||||||
|
#define SC_MAP_H
|
||||||
|
|
||||||
|
#include <memory.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#define sc_map_of_strkey(name, K, V) \
|
||||||
|
struct sc_map_item_##name \
|
||||||
|
{ \
|
||||||
|
K key; \
|
||||||
|
V value; \
|
||||||
|
uint32_t hash; \
|
||||||
|
}; \
|
||||||
|
\
|
||||||
|
sc_map_of(name, K, V)
|
||||||
|
|
||||||
|
#define sc_map_of_scalar(name, K, V) \
|
||||||
|
struct sc_map_item_##name \
|
||||||
|
{ \
|
||||||
|
K key; \
|
||||||
|
V value; \
|
||||||
|
}; \
|
||||||
|
\
|
||||||
|
sc_map_of(name, K, V)
|
||||||
|
|
||||||
|
#define sc_map_of(name, K, V) \
|
||||||
|
struct sc_map_##name \
|
||||||
|
{ \
|
||||||
|
struct sc_map_item_##name *mem; \
|
||||||
|
uint32_t cap; \
|
||||||
|
uint32_t size; \
|
||||||
|
uint32_t load_factor; \
|
||||||
|
uint32_t remap; \
|
||||||
|
V value; \
|
||||||
|
bool used; \
|
||||||
|
}; \
|
||||||
|
\
|
||||||
|
bool sc_map_init_##name(struct sc_map_##name *map, uint32_t cap, \
|
||||||
|
uint32_t load_factor); \
|
||||||
|
void sc_map_term_##name(struct sc_map_##name *map); \
|
||||||
|
uint32_t sc_map_size_##name(struct sc_map_##name *map); \
|
||||||
|
void sc_map_clear_##name(struct sc_map_##name *map); \
|
||||||
|
bool sc_map_put_##name(struct sc_map_##name *map, K key, V val); \
|
||||||
|
bool sc_map_get_##name(struct sc_map_##name *map, K key, V *value); \
|
||||||
|
bool sc_map_del_##name(struct sc_map_##name *map, K key, V* value);
|
||||||
|
|
||||||
|
#define sc_map_foreach(map, K, V) \
|
||||||
|
for (uint32_t __i = 0, __b = 0; __i < (map)->cap; __i++) \
|
||||||
|
for ((V) = (map)->mem[__i].value, (K) = (map)->mem[__i].key, __b = 1; \
|
||||||
|
__b && (V) != 0; __b = 0)
|
||||||
|
|
||||||
|
#define sc_map_foreach_key(map, K) \
|
||||||
|
for (uint32_t __i = 0, __b = 0; __i < (map)->cap; __i++) \
|
||||||
|
for ((K) = (map)->mem[__i].key, __b = 1; __b && (K) != 0; __b = 0)
|
||||||
|
|
||||||
|
#define sc_map_foreach_value(map, V) \
|
||||||
|
for (uint32_t __i = 0, __b = 0; __i < (map)->cap; __i++) \
|
||||||
|
for ((V) = (map)->mem[__i].value, __b = 1; __b && (V) != 0; __b = 0)
|
||||||
|
|
||||||
|
#define sc_map_calloc calloc
|
||||||
|
#define sc_map_free free
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
|
||||||
|
// name key type value type);
|
||||||
|
sc_map_of_scalar(32, uint32_t, uint32_t)
|
||||||
|
sc_map_of_scalar(64, uint64_t, uint64_t)
|
||||||
|
sc_map_of_scalar(64v, uint64_t, void *)
|
||||||
|
sc_map_of_scalar(64s, uint64_t, char *)
|
||||||
|
sc_map_of_strkey(str, char *, char *)
|
||||||
|
sc_map_of_strkey(sv, char *, void*)
|
||||||
|
sc_map_of_strkey(s64, char *, uint64_t)
|
||||||
|
|
||||||
|
// clang-format on
|
||||||
|
#endif
|
96
mutex/CMakeLists.txt
Normal file
96
mutex/CMakeLists.txt
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.5.1)
|
||||||
|
project(sc_mutex C)
|
||||||
|
|
||||||
|
set(CMAKE_C_STANDARD 99)
|
||||||
|
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||||
|
set(CMAKE_C_EXTENSIONS OFF)
|
||||||
|
|
||||||
|
add_executable(sc_mutex mutex_example.c sc_mutex.h sc_mutex.c)
|
||||||
|
|
||||||
|
if (NOT CMAKE_C_COMPILER_ID MATCHES "MSVC")
|
||||||
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -Wall -pedantic -Werror -D_GNU_SOURCE -pthread")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# --------------------- Test Configuration Start ---------------------------- #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
|
||||||
|
include(CTest)
|
||||||
|
include(CheckCCompilerFlag)
|
||||||
|
|
||||||
|
enable_testing()
|
||||||
|
|
||||||
|
add_executable(${PROJECT_NAME}_test mutex_test.c sc_mutex.c)
|
||||||
|
|
||||||
|
target_compile_options(${PROJECT_NAME}_test PRIVATE -DSC_SIZE_MAX=1400000ul)
|
||||||
|
|
||||||
|
if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||||
|
if ("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR
|
||||||
|
"${CMAKE_C_COMPILER_ID}" STREQUAL "GNU")
|
||||||
|
|
||||||
|
target_compile_options(${PROJECT_NAME}_test PRIVATE -DSC_HAVE_WRAP)
|
||||||
|
target_compile_options(${PROJECT_NAME}_test PRIVATE -fno-builtin)
|
||||||
|
target_link_options(${PROJECT_NAME}_test PRIVATE
|
||||||
|
-Wl,--wrap=pthread_mutexattr_init)
|
||||||
|
endif ()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
if ("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR
|
||||||
|
"${CMAKE_C_COMPILER_ID}" STREQUAL "AppleClang" OR
|
||||||
|
"${CMAKE_C_COMPILER_ID}" STREQUAL "GNU")
|
||||||
|
|
||||||
|
target_compile_options(${PROJECT_NAME}_test PRIVATE -fno-omit-frame-pointer)
|
||||||
|
|
||||||
|
if (SANITIZER)
|
||||||
|
target_compile_options(${PROJECT_NAME}_test PRIVATE -fsanitize=${SANITIZER})
|
||||||
|
target_link_options(${PROJECT_NAME}_test PRIVATE -fsanitize=${SANITIZER})
|
||||||
|
endif ()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
add_test(NAME ${PROJECT_NAME}_test COMMAND ${PROJECT_NAME}_test)
|
||||||
|
|
||||||
|
SET(MEMORYCHECK_COMMAND_OPTIONS
|
||||||
|
"-q --log-fd=2 --trace-children=yes --track-origins=yes \
|
||||||
|
--leak-check=full --show-leak-kinds=all --show-reachable=yes \
|
||||||
|
--error-exitcode=255")
|
||||||
|
|
||||||
|
add_custom_target(valgrind_${PROJECT_NAME} ${CMAKE_COMMAND}
|
||||||
|
-E env CTEST_OUTPUT_ON_FAILURE=1
|
||||||
|
${CMAKE_CTEST_COMMAND} -C $<CONFIG>
|
||||||
|
--overwrite MemoryCheckCommandOptions=${MEMORYCHECK_COMMAND_OPTIONS}
|
||||||
|
--verbose -T memcheck WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||||
|
|
||||||
|
add_custom_target(check_${PROJECT_NAME} ${CMAKE_COMMAND}
|
||||||
|
-E env CTEST_OUTPUT_ON_FAILURE=1
|
||||||
|
${CMAKE_CTEST_COMMAND} -C $<CONFIG> --verbose
|
||||||
|
WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||||
|
|
||||||
|
# ----------------------- - Code Coverage Start ----------------------------- #
|
||||||
|
|
||||||
|
if (${CMAKE_BUILD_TYPE} MATCHES "Coverage")
|
||||||
|
if ("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU")
|
||||||
|
target_compile_options(${PROJECT_NAME}_test PRIVATE --coverage)
|
||||||
|
target_link_libraries(${PROJECT_NAME}_test gcov)
|
||||||
|
else()
|
||||||
|
message(FATAL_ERROR "Only GCC is supported for coverage")
|
||||||
|
endif()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
add_custom_target(coverage_${PROJECT_NAME})
|
||||||
|
add_custom_command(
|
||||||
|
TARGET coverage_${PROJECT_NAME}
|
||||||
|
COMMAND lcov --capture --directory ..
|
||||||
|
--output-file coverage.info --rc lcov_branch_coverage=1
|
||||||
|
COMMAND lcov --remove coverage.info '/usr/*' '*example*' '*test*'
|
||||||
|
--output-file coverage.info --rc lcov_branch_coverage=1
|
||||||
|
COMMAND lcov --list coverage.info --rc lcov_branch_coverage=1
|
||||||
|
)
|
||||||
|
|
||||||
|
add_dependencies(coverage_${PROJECT_NAME} check_${PROJECT_NAME})
|
||||||
|
|
||||||
|
# -------------------------- Code Coverage End ------------------------------ #
|
||||||
|
|
||||||
|
|
||||||
|
# ----------------------- Test Configuration End ---------------------------- #
|
||||||
|
|
27
mutex/README.md
Normal file
27
mutex/README.md
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# Mutex
|
||||||
|
|
||||||
|
### Overview
|
||||||
|
|
||||||
|
- Mutex wrapper for Windows and POSIX systems.
|
||||||
|
- Just copy <b>sc_mutex.h</b> and <b>sc_mutex.c</b> to your project.
|
||||||
|
|
||||||
|
#### Usage
|
||||||
|
|
||||||
|
|
||||||
|
```c
|
||||||
|
#include "sc_mutex.h"
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
struct sc_mutex mutex;
|
||||||
|
|
||||||
|
sc_mutex_init(&mutex);
|
||||||
|
|
||||||
|
sc_mutex_lock(&mutex);
|
||||||
|
// Exclusive area.
|
||||||
|
sc_mutex_unlock(&mutex);
|
||||||
|
|
||||||
|
sc_mutex_term(&mutex);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
15
mutex/mutex_example.c
Normal file
15
mutex/mutex_example.c
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
#include "sc_mutex.h"
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
struct sc_mutex mutex;
|
||||||
|
|
||||||
|
sc_mutex_init(&mutex);
|
||||||
|
|
||||||
|
sc_mutex_lock(&mutex);
|
||||||
|
sc_mutex_unlock(&mutex);
|
||||||
|
|
||||||
|
sc_mutex_term(&mutex);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
57
mutex/mutex_test.c
Normal file
57
mutex/mutex_test.c
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020 Ozan Tezcan
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "sc_mutex.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#ifdef SC_HAVE_WRAP
|
||||||
|
bool mock_attrinit = false;
|
||||||
|
extern int __real_pthread_mutexattr_init(pthread_mutexattr_t *attr);
|
||||||
|
int __wrap_pthread_mutexattr_init(pthread_mutexattr_t *attr)
|
||||||
|
{
|
||||||
|
if (!mock_attrinit) {
|
||||||
|
return __real_pthread_mutexattr_init(attr);
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
struct sc_mutex mutex;
|
||||||
|
#ifdef SC_HAVE_WRAP
|
||||||
|
mock_attrinit = true;
|
||||||
|
assert(sc_mutex_init(&mutex) != 0);
|
||||||
|
mock_attrinit = false;
|
||||||
|
#endif
|
||||||
|
assert(sc_mutex_init(&mutex) == 0);
|
||||||
|
sc_mutex_lock(&mutex);
|
||||||
|
sc_mutex_unlock(&mutex);
|
||||||
|
assert(sc_mutex_term(&mutex) == 0);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
98
mutex/sc_mutex.c
Normal file
98
mutex/sc_mutex.c
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020 Ozan Tezcan
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "sc_mutex.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#if defined(_WIN32) || defined(_WIN64)
|
||||||
|
|
||||||
|
int sc_mutex_init(struct sc_mutex *mtx)
|
||||||
|
{
|
||||||
|
InitializeCriticalSection(&mtx->mtx);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int sc_mutex_term(struct sc_mutex *mtx)
|
||||||
|
{
|
||||||
|
DeleteCriticalSection(&mtx->mtx);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sc_mutex_lock(struct sc_mutex *mtx)
|
||||||
|
{
|
||||||
|
EnterCriticalSection(&mtx->mtx);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sc_mutex_unlock(struct sc_mutex *mtx)
|
||||||
|
{
|
||||||
|
LeaveCriticalSection(&mtx->mtx);
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
int sc_mutex_init(struct sc_mutex *mtx)
|
||||||
|
{
|
||||||
|
int rc;
|
||||||
|
pthread_mutexattr_t attr;
|
||||||
|
pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER;
|
||||||
|
|
||||||
|
mtx->mtx = mut;
|
||||||
|
|
||||||
|
// May fail on OOM
|
||||||
|
rc = pthread_mutexattr_init(&attr);
|
||||||
|
if (rc != 0) {
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This won't fail as long as we pass correct params.
|
||||||
|
rc = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);
|
||||||
|
assert (rc == 0);
|
||||||
|
|
||||||
|
// May fail on OOM
|
||||||
|
rc = pthread_mutex_init(&mtx->mtx, &attr);
|
||||||
|
|
||||||
|
// This won't fail as long as we pass correct param.
|
||||||
|
pthread_mutexattr_destroy(&attr);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
int sc_mutex_term(struct sc_mutex *mtx)
|
||||||
|
{
|
||||||
|
return pthread_mutex_destroy(&mtx->mtx);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sc_mutex_lock(struct sc_mutex *mtx)
|
||||||
|
{
|
||||||
|
// This won't fail as long as we pass correct param.
|
||||||
|
pthread_mutex_lock(&mtx->mtx);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sc_mutex_unlock(struct sc_mutex *mtx)
|
||||||
|
{
|
||||||
|
// This won't fail as long as we pass correct param.
|
||||||
|
pthread_mutex_unlock(&mtx->mtx);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
48
mutex/sc_mutex.h
Normal file
48
mutex/sc_mutex.h
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020 Ozan Tezcan
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef SC_MUTEX_H
|
||||||
|
#define SC_MUTEX_H
|
||||||
|
|
||||||
|
#if defined(_WIN32) || defined(_WIN64)
|
||||||
|
#include <windows.h>
|
||||||
|
#else
|
||||||
|
#include <pthread.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct sc_mutex
|
||||||
|
{
|
||||||
|
#if defined(_WIN32) || defined(_WIN64)
|
||||||
|
CRITICAL_SECTION mtx;
|
||||||
|
#else
|
||||||
|
pthread_mutex_t mtx;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
int sc_mutex_init(struct sc_mutex *mtx);
|
||||||
|
int sc_mutex_term(struct sc_mutex *mtx);
|
||||||
|
void sc_mutex_lock(struct sc_mutex *mtx);
|
||||||
|
void sc_mutex_unlock(struct sc_mutex *mtx);
|
||||||
|
|
||||||
|
#endif
|
12
perf/CMakeLists.txt
Normal file
12
perf/CMakeLists.txt
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.5.1)
|
||||||
|
project(sc_perf C)
|
||||||
|
|
||||||
|
set(CMAKE_C_STANDARD 99)
|
||||||
|
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||||
|
set(CMAKE_C_EXTENSIONS OFF)
|
||||||
|
|
||||||
|
if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||||
|
add_executable(sc_perf perf_example.c sc_perf.h sc_perf.c)
|
||||||
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -pedantic -Werror -D_GNU_SOURCE")
|
||||||
|
endif ()
|
||||||
|
|
73
perf/README.md
Normal file
73
perf/README.md
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
# Perf benchmark
|
||||||
|
|
||||||
|
### Overview
|
||||||
|
|
||||||
|
- Using <i>perf_event_open</i> to get hardware and software counters while you
|
||||||
|
are still inside your code.
|
||||||
|
- Only useful when you want to measure something inside the code really quick.
|
||||||
|
Otherwise, use <i>perf</i> itself.
|
||||||
|
- Linux only.
|
||||||
|
- All hardware and software counters are generated in the header file, you can
|
||||||
|
uncomment counters as you wish.
|
||||||
|
|
||||||
|
#### Usage
|
||||||
|
|
||||||
|
```c
|
||||||
|
|
||||||
|
#include "sc_perf.h"
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
sc_perf_start();
|
||||||
|
|
||||||
|
long_running_operation();
|
||||||
|
|
||||||
|
sc_perf_end();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
##### Output will be like
|
||||||
|
```
|
||||||
|
| Event | Value | Measurement time
|
||||||
|
---------------------------------------------------------------
|
||||||
|
| time (seconds) | 0.66 | (100,00%)
|
||||||
|
| cpu-clock | 654075766.00 | (100.00%)
|
||||||
|
| task-clock | 654077198.00 | (100.00%)
|
||||||
|
| page-faults | 3.00 | (100.00%)
|
||||||
|
| context-switches | 46.00 | (100.00%)
|
||||||
|
| cpu-migrations | 0.00 | (100.00%)
|
||||||
|
| page-fault-minor | 3.00 | (100.00%)
|
||||||
|
| cpu-cycles | 2656529748.00 | (100.00%)
|
||||||
|
| instructions | 7589235720.00 | (100.00%)
|
||||||
|
| cache-misses | 28715.00 | (100.00%)
|
||||||
|
| L1D-read-miss | 34124.00 | (100.00%)
|
||||||
|
| L1I-read-miss | 121958.00 | (100.00%)
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Pause example
|
||||||
|
```c
|
||||||
|
|
||||||
|
#include "sc_perf.h"
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
sc_perf_start();
|
||||||
|
|
||||||
|
long_running_operation();
|
||||||
|
|
||||||
|
//Will stop counters.
|
||||||
|
sc_perf_pause();
|
||||||
|
operation_you_dont_want_to_measure();
|
||||||
|
|
||||||
|
//Start counters again.
|
||||||
|
sc_perf_start();
|
||||||
|
another_long_running_operation();
|
||||||
|
|
||||||
|
sc_perf_end();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
21
perf/perf_example.c
Normal file
21
perf/perf_example.c
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
#include "sc_perf.h"
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
size_t total = 0;
|
||||||
|
|
||||||
|
sc_perf_start();
|
||||||
|
for (int i = 0; i < 100000000; i++) {
|
||||||
|
total += (rand() % 331) ^ 33;
|
||||||
|
}
|
||||||
|
sc_perf_pause();
|
||||||
|
|
||||||
|
for (int i = 0; i < 100000000; i++) {
|
||||||
|
total += (rand() % 327) ^ 37;
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_perf_end();
|
||||||
|
|
||||||
|
return total;
|
||||||
|
}
|
188
perf/sc_perf.c
Normal file
188
perf/sc_perf.c
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020 Ozan Tezcan
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
#include "sc_perf.h"
|
||||||
|
|
||||||
|
#include <sys/prctl.h>
|
||||||
|
|
||||||
|
#define ITEMS_SIZE (sizeof(sc_perf_hw) / sizeof(struct sc_perf_event))
|
||||||
|
|
||||||
|
static int initialized = 0;
|
||||||
|
static int running = 0;
|
||||||
|
static uint64_t total = 0;
|
||||||
|
static uint64_t start = 0;
|
||||||
|
|
||||||
|
struct sc_perf_item
|
||||||
|
{
|
||||||
|
struct sc_perf_event event;
|
||||||
|
double value;
|
||||||
|
double active;
|
||||||
|
int fd;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
static struct sc_perf_item sc_perf_items[ITEMS_SIZE];
|
||||||
|
|
||||||
|
#define sc_perf_assert(val) \
|
||||||
|
do { \
|
||||||
|
if (!(val)) { \
|
||||||
|
fprintf(stderr, "%s:%d: error", __FILE__, __LINE__); \
|
||||||
|
if (errno) { \
|
||||||
|
fprintf(stderr, " (%s)", strerror(errno)); \
|
||||||
|
} \
|
||||||
|
abort(); \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
static void sc_perf_set(struct sc_perf_item *items, size_t size)
|
||||||
|
{
|
||||||
|
const uint64_t flags =
|
||||||
|
PERF_FORMAT_TOTAL_TIME_ENABLED | PERF_FORMAT_TOTAL_TIME_RUNNING;
|
||||||
|
int fd;
|
||||||
|
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
struct perf_event_attr p = {.size = sizeof(struct perf_event_attr),
|
||||||
|
.read_format = flags,
|
||||||
|
.type = items[i].event.type,
|
||||||
|
.config = items[i].event.config,
|
||||||
|
.disabled = 1,
|
||||||
|
.inherit = 1,
|
||||||
|
.inherit_stat = 0,
|
||||||
|
.exclude_kernel = false,
|
||||||
|
.exclude_hv = false};
|
||||||
|
|
||||||
|
fd = syscall(__NR_perf_event_open, &p, 0, -1, -1, PERF_FLAG_FD_CLOEXEC);
|
||||||
|
if (fd == -1) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"Failed to set counter : %s , probably your system does "
|
||||||
|
"not support it! \n",
|
||||||
|
items[i].event.name);
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
items[i].fd = fd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sc_read(struct sc_perf_item *items, size_t size)
|
||||||
|
{
|
||||||
|
struct read_format
|
||||||
|
{
|
||||||
|
uint64_t value;
|
||||||
|
uint64_t time_enabled;
|
||||||
|
uint64_t time_running;
|
||||||
|
} fmt;
|
||||||
|
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
double n = 1.0;
|
||||||
|
|
||||||
|
sc_perf_assert(read(items[i].fd, &fmt, sizeof(fmt)) == sizeof(fmt));
|
||||||
|
|
||||||
|
if (fmt.time_enabled > 0 && fmt.time_running > 0) {
|
||||||
|
n = (double) fmt.time_running / (double) fmt.time_enabled;
|
||||||
|
items[i].active = n;
|
||||||
|
}
|
||||||
|
|
||||||
|
items[i].value += fmt.value * n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sc_perf_clear(void)
|
||||||
|
{
|
||||||
|
total = 0;
|
||||||
|
start = 0;
|
||||||
|
running = 0;
|
||||||
|
initialized = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < ITEMS_SIZE; i++) {
|
||||||
|
sc_perf_items[i].event = sc_perf_hw[i];
|
||||||
|
sc_perf_items[i].value = 0;
|
||||||
|
sc_perf_items[i].active = 0;
|
||||||
|
sc_perf_items[i].fd = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t sy_time_nano(void)
|
||||||
|
{
|
||||||
|
int rc;
|
||||||
|
struct timespec ts;
|
||||||
|
|
||||||
|
rc = clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
|
||||||
|
if (rc == -1) {
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
return ((uint64_t)(ts.tv_nsec + (ts.tv_sec * 1000 * 1000 * 1000)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void sc_perf_start(void)
|
||||||
|
{
|
||||||
|
if (!initialized) {
|
||||||
|
sc_perf_clear();
|
||||||
|
sc_perf_set(sc_perf_items, ITEMS_SIZE);
|
||||||
|
initialized = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_perf_assert(prctl(PR_TASK_PERF_EVENTS_ENABLE) == 0);
|
||||||
|
|
||||||
|
start = sy_time_nano();
|
||||||
|
running = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sc_perf_pause(void)
|
||||||
|
{
|
||||||
|
sc_perf_assert(initialized);
|
||||||
|
|
||||||
|
if (!running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_perf_assert(prctl(PR_TASK_PERF_EVENTS_DISABLE) == 0);
|
||||||
|
|
||||||
|
total += sy_time_nano() - start;
|
||||||
|
running = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sc_perf_end(void)
|
||||||
|
{
|
||||||
|
sc_perf_assert(initialized);
|
||||||
|
|
||||||
|
sc_perf_pause();
|
||||||
|
sc_read(sc_perf_items, ITEMS_SIZE);
|
||||||
|
|
||||||
|
for (int i = 0; i < ITEMS_SIZE; i++) {
|
||||||
|
close(sc_perf_items[i].fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("\n| %-25s | %-18s | %s \n", "Event", "Value", "Measurement time");
|
||||||
|
printf("---------------------------------------------------------------\n");
|
||||||
|
printf("| %-25s | %-18.2f | %s \n", "time (seconds)",
|
||||||
|
((double) total / 1e9), "(100,00%)");
|
||||||
|
|
||||||
|
for (int i = 0; i < ITEMS_SIZE; i++) {
|
||||||
|
printf("| %-25s | %-18.2f | (%.2f%%) \n", sc_perf_items[i].event.name,
|
||||||
|
sc_perf_items[i].value, sc_perf_items[i].active * 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_perf_clear();
|
||||||
|
}
|
127
perf/sc_perf.h
Normal file
127
perf/sc_perf.h
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020 Ozan Tezcan
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef SC_PERF_H
|
||||||
|
#define SC_PERF_H
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <linux/perf_event.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
#include <sys/syscall.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#define SC_PERF_HW_CACHE(CACHE, OP, RESULT) \
|
||||||
|
((PERF_COUNT_HW_CACHE_##CACHE) | (PERF_COUNT_HW_CACHE_OP_##OP << 8u) | \
|
||||||
|
(PERF_COUNT_HW_CACHE_RESULT_##RESULT << 16u))
|
||||||
|
|
||||||
|
struct sc_perf_event
|
||||||
|
{
|
||||||
|
char *name;
|
||||||
|
uint64_t type;
|
||||||
|
uint64_t config;
|
||||||
|
};
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
static const struct sc_perf_event sc_perf_hw[] = {
|
||||||
|
{"cpu-clock", PERF_TYPE_SOFTWARE, PERF_COUNT_SW_CPU_CLOCK },
|
||||||
|
{"task-clock", PERF_TYPE_SOFTWARE, PERF_COUNT_SW_TASK_CLOCK },
|
||||||
|
{"page-faults", PERF_TYPE_SOFTWARE, PERF_COUNT_SW_PAGE_FAULTS },
|
||||||
|
{"context-switches", PERF_TYPE_SOFTWARE, PERF_COUNT_SW_CONTEXT_SWITCHES },
|
||||||
|
{"cpu-migrations", PERF_TYPE_SOFTWARE, PERF_COUNT_SW_CPU_MIGRATIONS },
|
||||||
|
{"page-fault-minor", PERF_TYPE_SOFTWARE, PERF_COUNT_SW_PAGE_FAULTS_MIN },
|
||||||
|
// {"page-fault-major", PERF_TYPE_SOFTWARE, PERF_COUNT_SW_PAGE_FAULTS_MAJ },
|
||||||
|
// {"alignment-faults", PERF_TYPE_SOFTWARE, PERF_COUNT_SW_ALIGNMENT_FAULTS },
|
||||||
|
// {"emulation-faults", PERF_TYPE_SOFTWARE, PERF_COUNT_SW_EMULATION_FAULTS },
|
||||||
|
|
||||||
|
{"cpu-cycles", PERF_TYPE_HARDWARE, PERF_COUNT_HW_CPU_CYCLES },
|
||||||
|
{"instructions", PERF_TYPE_HARDWARE, PERF_COUNT_HW_INSTRUCTIONS },
|
||||||
|
// {"cache-references", PERF_TYPE_HARDWARE, PERF_COUNT_HW_CACHE_REFERENCES },
|
||||||
|
{"cache-misses", PERF_TYPE_HARDWARE, PERF_COUNT_HW_CACHE_MISSES },
|
||||||
|
// {"branch-instructions", PERF_TYPE_HARDWARE, PERF_COUNT_HW_BRANCH_INSTRUCTIONS },
|
||||||
|
// {"branch-misses", PERF_TYPE_HARDWARE, PERF_COUNT_HW_BRANCH_MISSES },
|
||||||
|
// {"bus-cycles", PERF_TYPE_HARDWARE, PERF_COUNT_HW_BUS_CYCLES },
|
||||||
|
// {"stalled-cycles-frontend", PERF_TYPE_HARDWARE, PERF_COUNT_HW_STALLED_CYCLES_FRONTEND },
|
||||||
|
// {"stalled-cycles-backend", PERF_TYPE_HARDWARE, PERF_COUNT_HW_STALLED_CYCLES_BACKEND },
|
||||||
|
// {"ref-cpu-cycles", PERF_TYPE_HARDWARE, PERF_COUNT_HW_REF_CPU_CYCLES },
|
||||||
|
|
||||||
|
// {"L1D-read-access", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(L1D, READ, ACCESS) },
|
||||||
|
{"L1D-read-miss", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(L1D, READ, MISS) },
|
||||||
|
// {"L1D-write-access", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(L1D, WRITE, ACCESS) },
|
||||||
|
// {"L1D-write-miss", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(L1D, WRITE, MISS) },
|
||||||
|
// {"L1D-prefetch-access", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(L1D, PREFETCH, ACCESS) },
|
||||||
|
// {"L1D-prefetch-miss", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(L1D, PREFETCH, MISS) },
|
||||||
|
// {"L1I-read-access", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(L1I, READ, ACCESS) },
|
||||||
|
{"L1I-read-miss", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(L1I, READ, MISS) },
|
||||||
|
// {"L1I-write-access", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(L1I, WRITE, ACCESS) },
|
||||||
|
// {"L1I-write-miss", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(L1I, WRITE, MISS) },
|
||||||
|
// {"L1I-prefetch-access", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(L1I, PREFETCH, ACCESS) },
|
||||||
|
// {"L1I-prefetch-miss", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(L1I, PREFETCH, MISS) },
|
||||||
|
// {"LL-read-access", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(LL, READ, ACCESS) },
|
||||||
|
// {"LL-read-miss", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(LL, READ, MISS) },
|
||||||
|
// {"LL-write-access", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(LL, WRITE, ACCESS) },
|
||||||
|
// {"LL-write-miss", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(LL, WRITE, MISS) },
|
||||||
|
// {"LL-prefetch-access", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(LL, PREFETCH, ACCESS) },
|
||||||
|
// {"LL-prefetch-miss", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(LL, PREFETCH, MISS) },
|
||||||
|
// {"DTLB-read-access", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(DTLB, READ, ACCESS) },
|
||||||
|
// {"DTLB-read-miss", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(DTLB, READ, MISS) },
|
||||||
|
// {"DTLB-write-access", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(DTLB, WRITE, ACCESS) },
|
||||||
|
// {"DTLB-write-miss", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(DTLB, WRITE, MISS) },
|
||||||
|
// {"DTLB-prefetch-access", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(DTLB, PREFETCH, ACCESS) },
|
||||||
|
// {"DTLB-prefetch-miss", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(DTLB, PREFETCH, MISS) },
|
||||||
|
// {"ITLB-read-access", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(ITLB, READ, ACCESS) },
|
||||||
|
// {"ITLB-read-miss", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(ITLB, READ, MISS) },
|
||||||
|
// {"ITLB-write-access", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(ITLB, WRITE, ACCESS) },
|
||||||
|
// {"ITLB-write-miss", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(ITLB, WRITE, MISS) },
|
||||||
|
// {"ITLB-prefetch-access", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(ITLB, PREFETCH, ACCESS) },
|
||||||
|
// {"ITLB-prefetch-miss", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(ITLB, PREFETCH, MISS) },
|
||||||
|
// {"BPU-read-access", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(BPU, READ, ACCESS) },
|
||||||
|
// {"BPU-read-miss", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(BPU, READ, MISS) },
|
||||||
|
// {"BPU-write-access", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(BPU, WRITE, ACCESS) },
|
||||||
|
// {"BPU-write-miss", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(BPU, WRITE, MISS) },
|
||||||
|
// {"BPU-prefetch-access", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(BPU, PREFETCH, ACCESS) },
|
||||||
|
// {"BPU-prefetch-miss", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(BPU, PREFETCH, MISS) },
|
||||||
|
// {"NODE-read-access", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(NODE, READ, ACCESS) },
|
||||||
|
// {"NODE-read-miss", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(NODE, READ, MISS) },
|
||||||
|
// {"NODE-write-access", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(NODE, WRITE, ACCESS) },
|
||||||
|
// {"NODE-write-miss", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(NODE, WRITE, MISS) },
|
||||||
|
// {"NODE-prefetch-access", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(NODE, PREFETCH, ACCESS) },
|
||||||
|
// {"NODE-prefetch-miss", PERF_TYPE_HW_CACHE, SC_PERF_HW_CACHE(NODE, PREFETCH, MISS) },
|
||||||
|
};
|
||||||
|
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
|
||||||
|
void sc_perf_start();
|
||||||
|
void sc_perf_pause();
|
||||||
|
void sc_perf_end();
|
||||||
|
|
||||||
|
#endif
|
97
queue/CMakeLists.txt
Normal file
97
queue/CMakeLists.txt
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.5.1)
|
||||||
|
project(sc_queue C)
|
||||||
|
|
||||||
|
set(CMAKE_C_STANDARD 99)
|
||||||
|
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||||
|
set(CMAKE_C_EXTENSIONS OFF)
|
||||||
|
|
||||||
|
add_executable(sc_queue queue_example.c sc_queue.h sc_queue.c)
|
||||||
|
|
||||||
|
if (NOT CMAKE_C_COMPILER_ID MATCHES "MSVC")
|
||||||
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -Wall -pedantic -Werror -D_GNU_SOURCE")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# --------------------- Test Configuration Start ---------------------------- #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
|
||||||
|
include(CTest)
|
||||||
|
include(CheckCCompilerFlag)
|
||||||
|
|
||||||
|
enable_testing()
|
||||||
|
|
||||||
|
add_executable(${PROJECT_NAME}_test queue_test.c sc_queue.c)
|
||||||
|
|
||||||
|
target_compile_options(${PROJECT_NAME}_test PRIVATE -DSC_SIZE_MAX=1400000ul)
|
||||||
|
|
||||||
|
if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||||
|
if ("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR
|
||||||
|
"${CMAKE_C_COMPILER_ID}" STREQUAL "GNU")
|
||||||
|
|
||||||
|
target_compile_options(${PROJECT_NAME}_test PRIVATE -fno-omit-frame-pointer)
|
||||||
|
target_compile_options(${PROJECT_NAME}_test PRIVATE -DSC_HAVE_WRAP)
|
||||||
|
target_compile_options(${PROJECT_NAME}_test PRIVATE -fno-builtin)
|
||||||
|
target_link_options(${PROJECT_NAME}_test PRIVATE -Wl,--wrap=realloc)
|
||||||
|
endif ()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
if ("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR
|
||||||
|
"${CMAKE_C_COMPILER_ID}" STREQUAL "AppleClang" OR
|
||||||
|
"${CMAKE_C_COMPILER_ID}" STREQUAL "GNU")
|
||||||
|
|
||||||
|
target_compile_options(${PROJECT_NAME}_test PRIVATE -fno-omit-frame-pointer)
|
||||||
|
|
||||||
|
if (SANITIZER)
|
||||||
|
target_compile_options(${PROJECT_NAME}_test PRIVATE -fsanitize=${SANITIZER})
|
||||||
|
target_link_options(${PROJECT_NAME}_test PRIVATE -fsanitize=${SANITIZER})
|
||||||
|
endif ()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
|
||||||
|
add_test(NAME ${PROJECT_NAME}_test COMMAND ${PROJECT_NAME}_test)
|
||||||
|
|
||||||
|
SET(MEMORYCHECK_COMMAND_OPTIONS
|
||||||
|
"-q --log-fd=2 --trace-children=yes --track-origins=yes \
|
||||||
|
--leak-check=full --show-leak-kinds=all --show-reachable=yes \
|
||||||
|
--error-exitcode=255")
|
||||||
|
|
||||||
|
add_custom_target(valgrind_${PROJECT_NAME} ${CMAKE_COMMAND}
|
||||||
|
-E env CTEST_OUTPUT_ON_FAILURE=1
|
||||||
|
${CMAKE_CTEST_COMMAND} -C $<CONFIG>
|
||||||
|
--overwrite MemoryCheckCommandOptions=${MEMORYCHECK_COMMAND_OPTIONS}
|
||||||
|
--verbose -T memcheck WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||||
|
|
||||||
|
add_custom_target(check_${PROJECT_NAME} ${CMAKE_COMMAND}
|
||||||
|
-E env CTEST_OUTPUT_ON_FAILURE=1
|
||||||
|
${CMAKE_CTEST_COMMAND} -C $<CONFIG> --verbose
|
||||||
|
WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||||
|
|
||||||
|
# ----------------------- - Code Coverage Start ----------------------------- #
|
||||||
|
|
||||||
|
if (${CMAKE_BUILD_TYPE} MATCHES "Coverage")
|
||||||
|
if ("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU")
|
||||||
|
target_compile_options(${PROJECT_NAME}_test PRIVATE --coverage)
|
||||||
|
target_link_libraries(${PROJECT_NAME}_test gcov)
|
||||||
|
else()
|
||||||
|
message(FATAL_ERROR "Only GCC is supported for coverage")
|
||||||
|
endif()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
add_custom_target(coverage_${PROJECT_NAME})
|
||||||
|
add_custom_command(
|
||||||
|
TARGET coverage_${PROJECT_NAME}
|
||||||
|
COMMAND lcov --capture --directory ..
|
||||||
|
--output-file coverage.info --rc lcov_branch_coverage=1
|
||||||
|
COMMAND lcov --remove coverage.info '/usr/*' '*example*' '*test*'
|
||||||
|
--output-file coverage.info --rc lcov_branch_coverage=1
|
||||||
|
COMMAND lcov --list coverage.info --rc lcov_branch_coverage=1
|
||||||
|
)
|
||||||
|
|
||||||
|
add_dependencies(coverage_${PROJECT_NAME} check_${PROJECT_NAME})
|
||||||
|
|
||||||
|
# -------------------------- Code Coverage End ------------------------------ #
|
||||||
|
|
||||||
|
|
||||||
|
# ----------------------- Test Configuration End ---------------------------- #
|
||||||
|
|
81
queue/README.md
Normal file
81
queue/README.md
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
# Generic queue
|
||||||
|
|
||||||
|
#### Overview
|
||||||
|
|
||||||
|
- Type generic queue which grows when you add elements.
|
||||||
|
- Add/remove from head/tail is possible so it can be used as list, stack,
|
||||||
|
queue, dequeue etc.
|
||||||
|
- Just copy <b>sc_queue.h</b> and <b>sc_queue.c</b> to your project.
|
||||||
|
|
||||||
|
|
||||||
|
##### Usage
|
||||||
|
|
||||||
|
|
||||||
|
```c
|
||||||
|
#include "sc_queue.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
int *queue;
|
||||||
|
int elem;
|
||||||
|
|
||||||
|
sc_queue_create(queue, 0);
|
||||||
|
|
||||||
|
sc_queue_add_last(queue, 2);
|
||||||
|
sc_queue_add_last(queue, 3);
|
||||||
|
sc_queue_add_last(queue, 4);
|
||||||
|
sc_queue_add_first(queue, 1);
|
||||||
|
|
||||||
|
sc_queue_foreach (queue, elem) {
|
||||||
|
printf("elem = [%d] \n", elem);
|
||||||
|
}
|
||||||
|
|
||||||
|
elem = sc_queue_remove_last(queue);
|
||||||
|
printf("Last element was : [%d] \n", elem);
|
||||||
|
|
||||||
|
elem = sc_queue_remove_first(queue);
|
||||||
|
printf("First element was : [%d] \n", elem);
|
||||||
|
|
||||||
|
sc_queue_destroy(queue);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
#### Internals
|
||||||
|
|
||||||
|
##### Memory
|
||||||
|
- Single allocation for all the data.
|
||||||
|
- Lazy allocation. No memory allocation until first 'add'.
|
||||||
|
|
||||||
|
##### Performance
|
||||||
|
- As all the items are in a single contiguous memory, it gives the best
|
||||||
|
performance you can expect.
|
||||||
|
- Keeps separate first and last element indexes, when you remove an element,
|
||||||
|
it doesn't move elements to fill the space.
|
||||||
|
|
||||||
|
##### Note
|
||||||
|
|
||||||
|
Queue pointer is not stable, it may change if we expand the memory. If you
|
||||||
|
pass the queue to another function which can add items, do it by passing
|
||||||
|
reference of the queue pointer :
|
||||||
|
|
||||||
|
```c
|
||||||
|
void some_function_to_add_elems(long **q)
|
||||||
|
{
|
||||||
|
sc_queue_add_last(*q, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
long *q;
|
||||||
|
|
||||||
|
sc_queue_create(q, 0);
|
||||||
|
sc_queue_add_last(q, 300);
|
||||||
|
|
||||||
|
some_function_to_add_elems(&q);
|
||||||
|
sc_array_destroy(q);
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
30
queue/queue_example.c
Normal file
30
queue/queue_example.c
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
#include "sc_queue.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
int *queue;
|
||||||
|
int elem;
|
||||||
|
|
||||||
|
sc_queue_create(queue, 0);
|
||||||
|
|
||||||
|
sc_queue_add_last(queue, 2);
|
||||||
|
sc_queue_add_last(queue, 3);
|
||||||
|
sc_queue_add_last(queue, 4);
|
||||||
|
sc_queue_add_first(queue, 1);
|
||||||
|
|
||||||
|
sc_queue_foreach (queue, elem) {
|
||||||
|
printf("elem = [%d] \n", elem);
|
||||||
|
}
|
||||||
|
|
||||||
|
elem = sc_queue_remove_last(queue);
|
||||||
|
printf("Last element was : [%d] \n", elem);
|
||||||
|
|
||||||
|
elem = sc_queue_remove_first(queue);
|
||||||
|
printf("First element was : [%d] \n", elem);
|
||||||
|
|
||||||
|
sc_queue_destroy(queue);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
159
queue/queue_test.c
Normal file
159
queue/queue_test.c
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
#include "sc_queue.h"
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#ifdef SC_HAVE_WRAP
|
||||||
|
|
||||||
|
bool fail_realloc = false;
|
||||||
|
void *__real_realloc(void *p, size_t size);
|
||||||
|
void *__wrap_realloc(void *p, size_t n)
|
||||||
|
{
|
||||||
|
if (fail_realloc) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return __real_realloc(p, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
void fail_test(void)
|
||||||
|
{
|
||||||
|
double *q;
|
||||||
|
|
||||||
|
fail_realloc = true;
|
||||||
|
assert(sc_queue_create(q, 1000) == false);
|
||||||
|
fail_realloc = false;
|
||||||
|
assert(sc_queue_create(q, 1000));
|
||||||
|
|
||||||
|
fail_realloc = true;
|
||||||
|
bool success = false;
|
||||||
|
for (int i = 0; i < 1024; i++) {
|
||||||
|
success = sc_queue_add_last(q, i);
|
||||||
|
if (!success) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(!success);
|
||||||
|
assert(sc_queue_size(q) == 1023);
|
||||||
|
fail_realloc = false;
|
||||||
|
assert(sc_queue_add_last(q, 1023) == true);
|
||||||
|
for (int i = 0; i < 1024; i++) {
|
||||||
|
assert(sc_queue_remove_first(q) == i);
|
||||||
|
}
|
||||||
|
assert(sc_queue_size(q) == 0);
|
||||||
|
assert(sc_queue_cap(q) == 2048);
|
||||||
|
|
||||||
|
fail_realloc = false;
|
||||||
|
|
||||||
|
size_t max = SC_SIZE_MAX / 2;
|
||||||
|
success = true;
|
||||||
|
for (size_t i = 0; i < max + 500; i++) {
|
||||||
|
success = sc_queue_add_last(q, i);
|
||||||
|
if (!success) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(!success);
|
||||||
|
sc_queue_clear(q);
|
||||||
|
assert(sc_queue_size(q) == 0);
|
||||||
|
sc_queue_destroy(q);
|
||||||
|
assert(sc_queue_create(q, max + 100) == false);
|
||||||
|
fail_realloc = false;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
void fail_test(void)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void example(void)
|
||||||
|
{
|
||||||
|
int *queue;
|
||||||
|
int elem;
|
||||||
|
|
||||||
|
sc_queue_create(queue, 0);
|
||||||
|
|
||||||
|
sc_queue_add_last(queue, 2);
|
||||||
|
sc_queue_add_last(queue, 3);
|
||||||
|
sc_queue_add_last(queue, 4);
|
||||||
|
sc_queue_add_first(queue, 1);
|
||||||
|
|
||||||
|
sc_queue_foreach (queue, elem) {
|
||||||
|
printf("elem = [%d] \n", elem);
|
||||||
|
}
|
||||||
|
|
||||||
|
elem = sc_queue_remove_last(queue);
|
||||||
|
printf("Last element was : [%d] \n", elem);
|
||||||
|
|
||||||
|
elem = sc_queue_remove_first(queue);
|
||||||
|
printf("First element was : [%d] \n", elem);
|
||||||
|
|
||||||
|
sc_queue_destroy(queue);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test1(void)
|
||||||
|
{
|
||||||
|
int count = 0;
|
||||||
|
int t;
|
||||||
|
int i = 0;
|
||||||
|
int *p;
|
||||||
|
|
||||||
|
assert(sc_queue_create(p, 0) == true);
|
||||||
|
|
||||||
|
sc_queue_foreach (p, t) {
|
||||||
|
(void) t;
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
assert(count == 0);
|
||||||
|
assert(sc_queue_empty(p) == true);
|
||||||
|
assert(sc_queue_size(p) == 0);
|
||||||
|
|
||||||
|
assert(sc_queue_add_first(p, 2) == true);
|
||||||
|
assert(sc_queue_add_first(p, 3) == true);
|
||||||
|
assert(sc_queue_add_first(p, 4) == true);
|
||||||
|
assert(sc_queue_add_first(p, 5) == true);
|
||||||
|
assert(sc_queue_add_first(p, 6) == true);
|
||||||
|
assert(sc_queue_add_last(p, 1) == true);
|
||||||
|
assert(sc_queue_add_last(p, 0) == true);
|
||||||
|
|
||||||
|
assert(sc_queue_empty(p) == false);
|
||||||
|
|
||||||
|
i = 6;
|
||||||
|
sc_queue_foreach (p, t) {
|
||||||
|
assert(t == i--);
|
||||||
|
count += t;
|
||||||
|
}
|
||||||
|
assert(count == 6 * 7 / 2);
|
||||||
|
assert(sc_queue_size(p) == 7);
|
||||||
|
|
||||||
|
assert(sc_queue_peek_first(p) == 6);
|
||||||
|
assert(sc_queue_size(p) == 7);
|
||||||
|
assert(sc_queue_peek_last(p) == 0);
|
||||||
|
assert(sc_queue_size(p) == 7);
|
||||||
|
|
||||||
|
t = sc_queue_remove_first(p);
|
||||||
|
assert(t == 6);
|
||||||
|
assert(sc_queue_size(p) == 6);
|
||||||
|
|
||||||
|
t = sc_queue_remove_last(p);
|
||||||
|
assert(t == 0);
|
||||||
|
assert(sc_queue_size(p) == 5);
|
||||||
|
|
||||||
|
sc_queue_clear(p);
|
||||||
|
assert(sc_queue_size(p) == 0);
|
||||||
|
assert(sc_queue_cap(p) == 8);
|
||||||
|
assert(sc_queue_empty(p) == true);
|
||||||
|
|
||||||
|
sc_queue_destroy(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
fail_test();
|
||||||
|
example();
|
||||||
|
test1();
|
||||||
|
return 0;
|
||||||
|
}
|
145
queue/sc_queue.c
Normal file
145
queue/sc_queue.c
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020 Ozan Tezcan
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "sc_queue.h"
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#ifndef SC_SIZE_MAX
|
||||||
|
#define SC_SIZE_MAX SIZE_MAX
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define SC_MAX_CAP ((SC_SIZE_MAX - sizeof(struct sc_queue)) / 2ul)
|
||||||
|
|
||||||
|
static const struct sc_queue sc_empty = {.cap = 1, .first = 0, .last = 0};
|
||||||
|
|
||||||
|
static void *queue_alloc(void *prev, size_t elem_size, size_t *cap)
|
||||||
|
{
|
||||||
|
size_t v = *cap;
|
||||||
|
void *t;
|
||||||
|
|
||||||
|
if (*cap > SC_MAX_CAP) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find next power of two.
|
||||||
|
v = v < 4 ? 4 : v;
|
||||||
|
v--;
|
||||||
|
for (size_t i = 1; i < sizeof(v) * 8; i *= 2) {
|
||||||
|
v |= v >> i;
|
||||||
|
}
|
||||||
|
v++;
|
||||||
|
|
||||||
|
t = sc_queue_realloc(prev, sizeof(struct sc_queue) + (elem_size * v));
|
||||||
|
*cap = v;
|
||||||
|
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sc_queue_init(void **q, size_t elem_size, size_t cap)
|
||||||
|
{
|
||||||
|
size_t p = cap;
|
||||||
|
struct sc_queue *meta;
|
||||||
|
|
||||||
|
if (cap == 0) {
|
||||||
|
*q = (void *) sc_empty.elems;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
meta = queue_alloc(NULL, elem_size, &p);
|
||||||
|
if (meta == NULL) {
|
||||||
|
*q = NULL;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
meta->cap = p;
|
||||||
|
meta->first = 0;
|
||||||
|
meta->last = 0;
|
||||||
|
*q = meta->elems;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sc_queue_term(void **q)
|
||||||
|
{
|
||||||
|
struct sc_queue *meta = sc_queue_meta(*q);
|
||||||
|
|
||||||
|
if (meta != &sc_empty) {
|
||||||
|
sc_queue_free(meta);
|
||||||
|
}
|
||||||
|
|
||||||
|
*q = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sc_queue_expand(void **q, size_t elem_size)
|
||||||
|
{
|
||||||
|
struct sc_queue *tmp;
|
||||||
|
struct sc_queue *meta = sc_queue_meta(*q);
|
||||||
|
size_t cap, count, size;
|
||||||
|
size_t pos = (meta->last + 1) & (meta->cap - 1);
|
||||||
|
uint8_t *e;
|
||||||
|
|
||||||
|
if (pos == meta->first) {
|
||||||
|
if (meta == &sc_empty) {
|
||||||
|
return sc_queue_init(q, elem_size, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
cap = meta->cap * 2;
|
||||||
|
tmp = queue_alloc(meta, elem_size, &cap);
|
||||||
|
if (tmp == NULL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move items to make empty slots at the end.
|
||||||
|
* e.g :
|
||||||
|
* last first
|
||||||
|
* | |
|
||||||
|
* Step 0 : | 2 | 3 | - | 1 | // tmp->cap : 4
|
||||||
|
* Step 1 : | 2 | 3 | - | 1 | - | - | - | - | // realloc
|
||||||
|
* Step 2 : | 2 | 3 | - | 1 | 1 | - | - | - | // mempcy
|
||||||
|
* Step 3 : | 2 | 2 | 3 | 1 | 1 | - | - | - | // memmove
|
||||||
|
* Step 4 : | 1 | 2 | 3 | 1 | 1 | - | - | - | // mempcy
|
||||||
|
* Step 5 : | 1 | 2 | 3 | - | - | - | - | - | // tmp->last = cap - 1;
|
||||||
|
* | |
|
||||||
|
* first last
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
e = tmp->elems;
|
||||||
|
count = tmp->cap - tmp->first;
|
||||||
|
size = elem_size;
|
||||||
|
|
||||||
|
memcpy(e + (size * tmp->cap), e + (size * tmp->first), count * size);
|
||||||
|
memmove(e + (count * size), e, tmp->first * size);
|
||||||
|
memcpy(e, e + (size * tmp->cap), count * size);
|
||||||
|
|
||||||
|
tmp->last = tmp->cap - 1;
|
||||||
|
tmp->first = 0;
|
||||||
|
tmp->cap = cap;
|
||||||
|
*q = tmp->elems;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
236
queue/sc_queue.h
Normal file
236
queue/sc_queue.h
Normal file
@ -0,0 +1,236 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020 Ozan Tezcan
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef SC_QUEUE_H
|
||||||
|
#define SC_QUEUE_H
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internals, do not use
|
||||||
|
*/
|
||||||
|
struct sc_queue
|
||||||
|
{
|
||||||
|
size_t cap;
|
||||||
|
size_t first;
|
||||||
|
size_t last;
|
||||||
|
uint8_t elems[];
|
||||||
|
};
|
||||||
|
|
||||||
|
#define sc_queue_meta(q) \
|
||||||
|
((struct sc_queue *) ((char *) (q) -offsetof(struct sc_queue, elems)))
|
||||||
|
|
||||||
|
static inline size_t sc_queue_inc_first(void *q)
|
||||||
|
{
|
||||||
|
struct sc_queue *meta = sc_queue_meta(q);
|
||||||
|
size_t tmp = meta->first;
|
||||||
|
|
||||||
|
meta->first = (meta->first + 1) & (meta->cap - 1);
|
||||||
|
return tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline size_t sc_queue_inc_last(void *q)
|
||||||
|
{
|
||||||
|
struct sc_queue *meta = sc_queue_meta(q);
|
||||||
|
size_t tmp = meta->last;
|
||||||
|
|
||||||
|
meta->last = (meta->last + 1) & (meta->cap - 1);
|
||||||
|
return tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline size_t sc_queue_dec_first(void *q)
|
||||||
|
{
|
||||||
|
struct sc_queue *meta = sc_queue_meta(q);
|
||||||
|
|
||||||
|
meta->first = (meta->first - 1) & (meta->cap - 1);
|
||||||
|
return meta->first;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline size_t sc_queue_dec_last(void *q)
|
||||||
|
{
|
||||||
|
struct sc_queue *meta = sc_queue_meta(q);
|
||||||
|
|
||||||
|
meta->last = (meta->last - 1) & (meta->cap - 1);
|
||||||
|
return meta->last;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sc_queue_init(void **q, size_t elem_size, size_t cap);
|
||||||
|
void sc_queue_term(void **q);
|
||||||
|
bool sc_queue_expand(void **q, size_t elem_size);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plug your allocator if you want.
|
||||||
|
*/
|
||||||
|
#define sc_queue_realloc realloc
|
||||||
|
#define sc_queue_free free
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param q Queue pointer
|
||||||
|
* @param count Initial capacity, '0' is a valid value if you don't want to
|
||||||
|
* allocate memory immediately.
|
||||||
|
*/
|
||||||
|
#define sc_queue_create(q, count) \
|
||||||
|
sc_queue_init((void **) &(q), sizeof(*(q)), count)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deallocate underlying memory.
|
||||||
|
* @param q Queue pointer
|
||||||
|
*/
|
||||||
|
#define sc_queue_destroy(q) sc_queue_term(((void **) &(q)))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param q Queue pointer
|
||||||
|
* @return Current capacity
|
||||||
|
*/
|
||||||
|
#define sc_queue_cap(q) (sc_queue_meta((q))->cap)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param q Queue pointer
|
||||||
|
* @return Element count
|
||||||
|
*/
|
||||||
|
#define sc_queue_size(q) \
|
||||||
|
((sc_queue_meta(q)->last - sc_queue_meta(q)->first) & \
|
||||||
|
(sc_queue_meta(q)->cap - 1))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param q Queue pointer
|
||||||
|
* @return true if queue is empty
|
||||||
|
*/
|
||||||
|
#define sc_queue_empty(q) ((sc_queue_meta(q)->last == sc_queue_meta(q)->first))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the queue without deallocating underlying memory.
|
||||||
|
* @param q Queue pointer
|
||||||
|
*/
|
||||||
|
#define sc_queue_clear(q) \
|
||||||
|
do { \
|
||||||
|
sc_queue_meta(q)->first = 0; \
|
||||||
|
sc_queue_meta(q)->last = 0; \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param q Queue pointer
|
||||||
|
* @return Index of the first element. If queue is empty, result is undefined.
|
||||||
|
*/
|
||||||
|
#define sc_queue_first(q) (sc_queue_meta(q)->first)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param q Queue pointer
|
||||||
|
* @return Index of the last element. If queue is empty, result is undefined.
|
||||||
|
*/
|
||||||
|
#define sc_queue_last(q) (sc_queue_meta(q)->last)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Index of the next element after i, if there is no element after i
|
||||||
|
* result is undefined.
|
||||||
|
*/
|
||||||
|
#define sc_queue_next(q, i) (((i) + 1) & (sc_queue_meta(q)->cap - 1))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns element at 'i'th position, so regular loops are possible :
|
||||||
|
*
|
||||||
|
* for (size_t i = 0; i < sc_queue_size(q); i++) {
|
||||||
|
* printf("%d" \n, sc_queue_at(q, i));
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* @param q Queue pointer
|
||||||
|
* @return element at 'i'th position
|
||||||
|
*/
|
||||||
|
#define sc_queue_at(q, i) \
|
||||||
|
(q)[((sc_queue_meta(q)->first) + (i)) & (sc_queue_cap(q) - 1)]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param q Queue pointer
|
||||||
|
* @return First element without removing from the queue.
|
||||||
|
* If queue is empty, result is undefined
|
||||||
|
*/
|
||||||
|
#define sc_queue_peek_first(q) ((q)[sc_queue_meta(q)->first])
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param q Queue pointer
|
||||||
|
* @return Last element without removing from the queue.
|
||||||
|
* If queue is empty, result is undefined
|
||||||
|
*/
|
||||||
|
#define sc_queue_peek_last(q) \
|
||||||
|
(q)[(sc_queue_meta(q)->last - 1) & (sc_queue_meta(q)->cap - 1)]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param q Queue pointer
|
||||||
|
* @param elem Elem to be added at the end of the list
|
||||||
|
* @return 'true' on success, 'false' on out of memory.
|
||||||
|
*/
|
||||||
|
#define sc_queue_add_last(q, elem) \
|
||||||
|
sc_queue_expand((void **) &(q), sizeof(*(q))) == true ? \
|
||||||
|
(q)[sc_queue_inc_last((q))] = (elem), \
|
||||||
|
true : false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param q Queue pointer
|
||||||
|
* @return Remove the last element from the queue and return its value.
|
||||||
|
* If queue is empty, result is undefined.
|
||||||
|
*/
|
||||||
|
#define sc_queue_remove_last(q) ((q)[sc_queue_dec_last((q))])
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param q Queue pointer.
|
||||||
|
* @param elem Elem to be added at the head of the list.
|
||||||
|
* @return 'true' on success, 'false' on out of memory.
|
||||||
|
*/
|
||||||
|
#define sc_queue_add_first(q, elem) \
|
||||||
|
sc_queue_expand((void **) &(q), sizeof(*(q))) == true ? \
|
||||||
|
(q)[sc_queue_dec_first((q))] = (elem), \
|
||||||
|
true : false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param q Queue pointer
|
||||||
|
* @return Remove the first element from the queue and return its value.
|
||||||
|
* If queue is empty, result is undefined.
|
||||||
|
*/
|
||||||
|
#define sc_queue_remove_first(q) (q)[sc_queue_inc_first((q))]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For each loop,
|
||||||
|
*
|
||||||
|
* int *queue;
|
||||||
|
* sc_queue_create(queue, 4);
|
||||||
|
*
|
||||||
|
* int elem;
|
||||||
|
* sc_queue_foreach(queue, elem) {
|
||||||
|
* printf("Elem : %d \n, elem);
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
#define sc_queue_foreach(q, elem) \
|
||||||
|
if (!sc_queue_empty(q)) { \
|
||||||
|
(elem) = (q)[sc_queue_first(q)]; \
|
||||||
|
} \
|
||||||
|
for (size_t _i = sc_queue_first(q); _i != sc_queue_last(q); \
|
||||||
|
_i = sc_queue_next(q, _i), (elem) = (q)[_i])
|
||||||
|
|
||||||
|
#endif
|
98
string/CMakeLists.txt
Normal file
98
string/CMakeLists.txt
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.5.1)
|
||||||
|
project(sc_str C)
|
||||||
|
|
||||||
|
set(CMAKE_C_STANDARD 99)
|
||||||
|
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||||
|
set(CMAKE_C_EXTENSIONS OFF)
|
||||||
|
|
||||||
|
add_executable(sc_str str_example.c sc_str.h sc_str.c)
|
||||||
|
|
||||||
|
if (NOT CMAKE_C_COMPILER_ID MATCHES "MSVC")
|
||||||
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -Wall -pedantic -Werror -D_GNU_SOURCE")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# --------------------- Test Configuration Start ---------------------------- #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
|
||||||
|
include(CTest)
|
||||||
|
include(CheckCCompilerFlag)
|
||||||
|
|
||||||
|
enable_testing()
|
||||||
|
|
||||||
|
add_executable(${PROJECT_NAME}_test str_test.c sc_str.c)
|
||||||
|
|
||||||
|
target_compile_options(${PROJECT_NAME}_test PRIVATE -DSC_SIZE_MAX=4000ul)
|
||||||
|
|
||||||
|
if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||||
|
if ("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR
|
||||||
|
"${CMAKE_C_COMPILER_ID}" STREQUAL "GNU")
|
||||||
|
|
||||||
|
target_compile_options(${PROJECT_NAME}_test PRIVATE -DSC_HAVE_WRAP)
|
||||||
|
target_compile_options(${PROJECT_NAME}_test PRIVATE -fno-builtin)
|
||||||
|
target_link_options(${PROJECT_NAME}_test PRIVATE
|
||||||
|
-Wl,--wrap=malloc,--wrap=vsnprintf,--wrap=realloc)
|
||||||
|
|
||||||
|
target_link_options(${PROJECT_NAME}_test PRIVATE
|
||||||
|
-Wl,--wrap=malloc,--wrap=vsnprintf,--wrap=realloc)
|
||||||
|
endif ()
|
||||||
|
endif ()
|
||||||
|
if ("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR
|
||||||
|
"${CMAKE_C_COMPILER_ID}" STREQUAL "AppleClang" OR
|
||||||
|
"${CMAKE_C_COMPILER_ID}" STREQUAL "GNU")
|
||||||
|
|
||||||
|
target_compile_options(${PROJECT_NAME}_test PRIVATE -fno-omit-frame-pointer)
|
||||||
|
|
||||||
|
if (SANITIZER)
|
||||||
|
target_compile_options(${PROJECT_NAME}_test PRIVATE -fsanitize=${SANITIZER})
|
||||||
|
target_link_options(${PROJECT_NAME}_test PRIVATE -fsanitize=${SANITIZER})
|
||||||
|
endif ()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
add_test(NAME ${PROJECT_NAME}_test COMMAND ${PROJECT_NAME}_test)
|
||||||
|
|
||||||
|
SET(MEMORYCHECK_COMMAND_OPTIONS
|
||||||
|
"-q --log-fd=2 --trace-children=yes --track-origins=yes \
|
||||||
|
--leak-check=full --show-leak-kinds=all --show-reachable=yes \
|
||||||
|
--error-exitcode=255")
|
||||||
|
|
||||||
|
add_custom_target(valgrind_${PROJECT_NAME} ${CMAKE_COMMAND}
|
||||||
|
-E env CTEST_OUTPUT_ON_FAILURE=1
|
||||||
|
${CMAKE_CTEST_COMMAND} -C $<CONFIG>
|
||||||
|
--overwrite MemoryCheckCommandOptions=${MEMORYCHECK_COMMAND_OPTIONS}
|
||||||
|
--verbose -T memcheck WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||||
|
|
||||||
|
add_custom_target(check_${PROJECT_NAME} ${CMAKE_COMMAND}
|
||||||
|
-E env CTEST_OUTPUT_ON_FAILURE=1
|
||||||
|
${CMAKE_CTEST_COMMAND} -C $<CONFIG> --verbose
|
||||||
|
WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||||
|
|
||||||
|
# ----------------------- - Code Coverage Start ----------------------------- #
|
||||||
|
|
||||||
|
if (${CMAKE_BUILD_TYPE} MATCHES "Coverage")
|
||||||
|
if ("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU")
|
||||||
|
target_compile_options(${PROJECT_NAME}_test PRIVATE --coverage)
|
||||||
|
target_link_libraries(${PROJECT_NAME}_test gcov)
|
||||||
|
else()
|
||||||
|
message(FATAL_ERROR "Only GCC is supported for coverage")
|
||||||
|
endif()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
add_custom_target(coverage_${PROJECT_NAME})
|
||||||
|
add_custom_command(
|
||||||
|
TARGET coverage_${PROJECT_NAME}
|
||||||
|
COMMAND lcov --capture --directory ..
|
||||||
|
--output-file coverage.info --rc lcov_branch_coverage=1
|
||||||
|
COMMAND lcov --remove coverage.info '/usr/*' '*example*' '*test*'
|
||||||
|
--output-file coverage.info --rc lcov_branch_coverage=1
|
||||||
|
COMMAND lcov --list coverage.info --rc lcov_branch_coverage=1
|
||||||
|
)
|
||||||
|
|
||||||
|
add_dependencies(coverage_${PROJECT_NAME} check_${PROJECT_NAME})
|
||||||
|
|
||||||
|
# -------------------------- Code Coverage End ------------------------------ #
|
||||||
|
|
||||||
|
|
||||||
|
# ----------------------- Test Configuration End ---------------------------- #
|
||||||
|
|
39
string/README.md
Normal file
39
string/README.md
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
# Length prefixed string
|
||||||
|
|
||||||
|
Length prefixed C strings, length is at the start of the allocated memory
|
||||||
|
|
||||||
|
e.g :
|
||||||
|
-----------------------------------------------
|
||||||
|
| 0 | 0 | 0 | 4 | 'T' | 'E' | 'S' | 'T' | '\0'|
|
||||||
|
-----------------------------------------------
|
||||||
|
^
|
||||||
|
return
|
||||||
|
User can keep pointer to first character, so it's like C style strings with
|
||||||
|
additional functionality when it's used with these functions here.
|
||||||
|
|
||||||
|
##### Pros
|
||||||
|
- User gets a null terminated `char*`, so it still works with c style string
|
||||||
|
functions, e.g printf, strcmp.
|
||||||
|
- Faster length access and copy.
|
||||||
|
- Provides a few more functions to make easier create/append/trim/substring
|
||||||
|
operations.
|
||||||
|
|
||||||
|
##### Cons
|
||||||
|
- 4 bytes fixed overhead per string.
|
||||||
|
|
||||||
|
##### Memory
|
||||||
|
- 4 bytes fixed overhead per string.
|
||||||
|
|
||||||
|
##### Performance
|
||||||
|
- Faster length access and copy.
|
||||||
|
- When you create/set/append a string new memory is allocated. If you are
|
||||||
|
modifying strings a lot, consider using buffer-like implementation for that if
|
||||||
|
performance is critical for your use-case. I modify strings rarely but access
|
||||||
|
a lot (copy/move etc.), so ease of use and read/copy/move performance was
|
||||||
|
primary goal for this implementation.
|
||||||
|
|
||||||
|
|
||||||
|
```c
|
||||||
|
|
||||||
|
|
||||||
|
```
|
369
string/sc_str.c
Normal file
369
string/sc_str.c
Normal file
@ -0,0 +1,369 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020 Ozan Tezcan
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "sc_str.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* String with 'length' at the start of the allocated memory
|
||||||
|
* e.g :
|
||||||
|
* -----------------------------------------------
|
||||||
|
* | 0 | 0 | 0 | 4 | 'T' | 'E' | 'S' | 'T' | '\0'|
|
||||||
|
* -----------------------------------------------
|
||||||
|
*
|
||||||
|
* User can keep pointer to first character, so it's like C style strings with
|
||||||
|
* additional functionality when it's used with these functions here.
|
||||||
|
*/
|
||||||
|
struct sc_str
|
||||||
|
{
|
||||||
|
uint32_t len;
|
||||||
|
char buf[];
|
||||||
|
};
|
||||||
|
|
||||||
|
#define sc_str_meta(str) \
|
||||||
|
((struct sc_str *) ((char *) (str) -offsetof(struct sc_str, buf)))
|
||||||
|
|
||||||
|
#define sc_str_bytes(n) ((n) + sizeof(struct sc_str) + 1)
|
||||||
|
#ifndef SC_SIZE_MAX
|
||||||
|
#define SC_SIZE_MAX (UINT32_MAX - sizeof(struct sc_str) - 1)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
char *sc_str_create(const char *str)
|
||||||
|
{
|
||||||
|
assert(str != NULL);
|
||||||
|
|
||||||
|
size_t size = strlen(str);
|
||||||
|
if (size > SC_SIZE_MAX) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sc_str_create_len(str, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
char *sc_str_create_len(const char *str, uint32_t len)
|
||||||
|
{
|
||||||
|
assert(str != NULL);
|
||||||
|
|
||||||
|
struct sc_str *copy;
|
||||||
|
|
||||||
|
copy = sc_str_malloc(sc_str_bytes(len));
|
||||||
|
if (copy == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(copy->buf, str, len);
|
||||||
|
copy->buf[len] = '\0';
|
||||||
|
copy->len = len;
|
||||||
|
|
||||||
|
return copy->buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *sc_str_create_va(const char *fmt, va_list va)
|
||||||
|
{
|
||||||
|
int rc;
|
||||||
|
char tmp[1024];
|
||||||
|
struct sc_str *str;
|
||||||
|
va_list args;
|
||||||
|
|
||||||
|
va_copy(args, va);
|
||||||
|
rc = vsnprintf(tmp, sizeof(tmp), fmt, args);
|
||||||
|
if (rc < 0) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
va_end(args);
|
||||||
|
|
||||||
|
str = sc_str_malloc(sc_str_bytes(rc));
|
||||||
|
if (str == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
str->len = rc;
|
||||||
|
|
||||||
|
if (rc < sizeof(tmp)) {
|
||||||
|
memcpy(str->buf, tmp, str->len + 1);
|
||||||
|
} else {
|
||||||
|
va_copy(args, va);
|
||||||
|
rc = vsnprintf(str->buf, str->len, fmt, args);
|
||||||
|
va_end(args);
|
||||||
|
|
||||||
|
if (rc < 0 || rc > str->len) {
|
||||||
|
sc_str_free(str);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return str->buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *sc_str_create_fmt(const char *fmt, ...)
|
||||||
|
{
|
||||||
|
char *str;
|
||||||
|
va_list args;
|
||||||
|
|
||||||
|
va_start(args, fmt);
|
||||||
|
str = sc_str_create_va(fmt, args);
|
||||||
|
va_end(args);
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sc_str_destroy(char *str)
|
||||||
|
{
|
||||||
|
if (str == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_str_free(sc_str_meta(str));
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t sc_str_len(const char *str)
|
||||||
|
{
|
||||||
|
if (str == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sc_str_meta(str)->len;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *sc_str_dup(const char *str)
|
||||||
|
{
|
||||||
|
if (str == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sc_str_create_len(str, sc_str_meta(str)->len);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sc_str_set(char **str, const char *param)
|
||||||
|
{
|
||||||
|
char *copy = sc_str_create(param);
|
||||||
|
if (copy == NULL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_str_destroy(*str);
|
||||||
|
*str = copy;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sc_str_set_fmt(char **str, const char *fmt, ...)
|
||||||
|
{
|
||||||
|
char *ret;
|
||||||
|
va_list args;
|
||||||
|
|
||||||
|
va_start(args, fmt);
|
||||||
|
ret = sc_str_create_va(fmt, args);
|
||||||
|
va_end(args);
|
||||||
|
|
||||||
|
if (ret != NULL) {
|
||||||
|
sc_str_destroy(*str);
|
||||||
|
*str = ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret != NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sc_str_append(char **str, const char *param)
|
||||||
|
{
|
||||||
|
struct sc_str *meta = sc_str_meta(*str);
|
||||||
|
size_t len = strlen(param);
|
||||||
|
size_t alloc = sc_str_bytes(meta->len + len);
|
||||||
|
|
||||||
|
if (alloc > SC_SIZE_MAX || (meta = sc_str_realloc(meta, alloc)) == NULL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(&meta->buf[meta->len], param, len);
|
||||||
|
meta->len += len;
|
||||||
|
meta->buf[meta->len] = '\0';
|
||||||
|
*str = meta->buf;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sc_str_cmp(const char *str, const char *other)
|
||||||
|
{
|
||||||
|
assert(str != NULL);
|
||||||
|
assert(other != NULL);
|
||||||
|
|
||||||
|
struct sc_str *s1 = sc_str_meta(str);
|
||||||
|
struct sc_str *s2 = sc_str_meta(other);
|
||||||
|
|
||||||
|
return s1->len == s2->len && !memcmp(s1->buf, s2->buf, s1->len);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void swap(char *str, char *d)
|
||||||
|
{
|
||||||
|
char tmp;
|
||||||
|
char *c = str + sc_str_meta(str)->len;
|
||||||
|
|
||||||
|
tmp = *c;
|
||||||
|
*c = *d;
|
||||||
|
*d = tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *sc_str_token_begin(char *str, char **save, const char *delim)
|
||||||
|
{
|
||||||
|
char *it = str;
|
||||||
|
|
||||||
|
if (*save != NULL) {
|
||||||
|
it = *save;
|
||||||
|
swap(str, it);
|
||||||
|
if (*it == '\0') {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
it++;
|
||||||
|
}
|
||||||
|
|
||||||
|
*save = it + strcspn(it, delim);
|
||||||
|
swap(str, *save);
|
||||||
|
|
||||||
|
return it;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sc_str_token_end(char *str, char **save)
|
||||||
|
{
|
||||||
|
char *end = str + sc_str_meta(str)->len;
|
||||||
|
|
||||||
|
if (*end == '\0') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
swap(str, (save != NULL && *save != NULL) ? *save : str + strlen(str));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sc_str_trim(char **str, char *list)
|
||||||
|
{
|
||||||
|
char *start = *str + strspn(*str, list);
|
||||||
|
char *end = start + strcspn(start, list);
|
||||||
|
|
||||||
|
if (start != *str || end != *str) {
|
||||||
|
start = sc_str_create_len(start, end - start);
|
||||||
|
if (start == NULL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_str_destroy(*str);
|
||||||
|
*str = start;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sc_str_substring(char **str, uint32_t start, uint32_t end)
|
||||||
|
{
|
||||||
|
char *c;
|
||||||
|
struct sc_str *meta = sc_str_meta(*str);
|
||||||
|
|
||||||
|
if (start > meta->len || end > meta->len || start > end) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
c = sc_str_create_len(*str + start, end - start);
|
||||||
|
if (c == NULL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_str_destroy(*str);
|
||||||
|
*str = c;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sc_str_replace(char **str, const char *replace, const char *with)
|
||||||
|
{
|
||||||
|
assert(replace != NULL && with != NULL);
|
||||||
|
assert(str != NULL && *str != NULL);
|
||||||
|
|
||||||
|
size_t replace_len = strlen(replace);
|
||||||
|
size_t with_len = strlen(with);
|
||||||
|
if (replace_len > UINT32_MAX || with_len > UINT32_MAX) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int64_t diff = (int64_t)with_len - replace_len;
|
||||||
|
size_t len_unmatch;
|
||||||
|
size_t count, size;
|
||||||
|
struct sc_str *dest;
|
||||||
|
struct sc_str *meta = sc_str_meta(*str);
|
||||||
|
char *orig = *str;
|
||||||
|
char *orig_end = *str + meta->len;
|
||||||
|
char *tmp;
|
||||||
|
|
||||||
|
// Fast path, same size replacement.
|
||||||
|
if (diff == 0) {
|
||||||
|
while ((orig = strstr(orig, replace)) != NULL) {
|
||||||
|
memcpy(orig, with, replace_len);
|
||||||
|
orig += replace_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate new string size.
|
||||||
|
tmp = orig;
|
||||||
|
size = meta->len;
|
||||||
|
for (count = 0; (tmp = strstr(tmp, replace)) != NULL; count++) {
|
||||||
|
tmp += replace_len;
|
||||||
|
// Check overflow.
|
||||||
|
if (size > SC_SIZE_MAX - diff) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
size += diff;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No match.
|
||||||
|
if (count == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
dest = sc_str_malloc(sc_str_bytes(size));
|
||||||
|
if (!dest) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
dest->len = size;
|
||||||
|
tmp = dest->buf;
|
||||||
|
|
||||||
|
while (count--) {
|
||||||
|
len_unmatch = strstr(orig, replace) - orig;
|
||||||
|
memcpy(tmp, orig, len_unmatch);
|
||||||
|
tmp += len_unmatch;
|
||||||
|
|
||||||
|
memcpy(tmp, with, with_len);
|
||||||
|
tmp += with_len;
|
||||||
|
orig += len_unmatch + replace_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(tmp, orig, orig_end - orig + 1);
|
||||||
|
|
||||||
|
sc_str_destroy(*str);
|
||||||
|
*str = dest->buf;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
198
string/sc_str.h
Normal file
198
string/sc_str.h
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020 Ozan Tezcan
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef SC_STR_H
|
||||||
|
#define SC_STR_H
|
||||||
|
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Length prefixed C strings, length is at the start of the allocated memory
|
||||||
|
* e.g :
|
||||||
|
* -----------------------------------------------
|
||||||
|
* | 0 | 0 | 0 | 4 | 'T' | 'E' | 'S' | 'T' | '\0'|
|
||||||
|
* -----------------------------------------------
|
||||||
|
* ^
|
||||||
|
* return
|
||||||
|
* User can keep pointer to first character, so it's like C style strings with
|
||||||
|
* additional functionality when it's used with these functions here.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plug your allocators.
|
||||||
|
*/
|
||||||
|
#define sc_str_malloc malloc
|
||||||
|
#define sc_str_realloc realloc
|
||||||
|
#define sc_str_free free
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param str '\0' terminated C string, must not be NULL.
|
||||||
|
* @return Length prefixed string. NULL on out of memory.
|
||||||
|
*/
|
||||||
|
char *sc_str_create(const char *str);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param str C string, no need for '\0' termination, must not be NULL.
|
||||||
|
* @param len Length of the 'str'.
|
||||||
|
* @return Length prefixed string. NULL on out of memory.
|
||||||
|
*/
|
||||||
|
char *sc_str_create_len(const char *str, uint32_t len);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* printf-style string creation.
|
||||||
|
*
|
||||||
|
* @param fmt Format
|
||||||
|
* @param ... Arguments
|
||||||
|
* @return Length prefixed string. NULL on out of memory.
|
||||||
|
*/
|
||||||
|
char *sc_str_create_fmt(const char *fmt, ...);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* vprintf-style string creation.
|
||||||
|
*
|
||||||
|
* @param fmt Format
|
||||||
|
* @param va va_list
|
||||||
|
* @return Length prefixed string. NULL on out of memory.
|
||||||
|
*/
|
||||||
|
char *sc_str_create_va(const char *fmt, va_list va);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deallocate length prefixed string.
|
||||||
|
*
|
||||||
|
* @param str Length prefixed string. NULL values are accepted.
|
||||||
|
*/
|
||||||
|
void sc_str_destroy(char *str);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param str Length prefixed string. NULL values are accepted.
|
||||||
|
* @return Length of the string. If NULL, '-1' will be returned.
|
||||||
|
*/
|
||||||
|
int64_t sc_str_len(const char *str);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param str Length prefixed string. NULL values are accepted.
|
||||||
|
* @return Length of the duplicate. NULL on out of memory.
|
||||||
|
*/
|
||||||
|
char *sc_str_dup(const char *str);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param str Pointer to length prefixed string.
|
||||||
|
* @param param New value to set.
|
||||||
|
* @return 'false' on out of memory.
|
||||||
|
* 'true' on success, '*str' may change.
|
||||||
|
*/
|
||||||
|
bool sc_str_set(char **str, const char *param);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param str Pointer to length prefixed string.
|
||||||
|
* @param fmt Format
|
||||||
|
* @param ... Arguments
|
||||||
|
* @return 'false' on out of memory, previous value will remain intact.
|
||||||
|
* 'true' on success, '*str' may change.
|
||||||
|
*/
|
||||||
|
bool sc_str_set_fmt(char **str, const char *fmt, ...);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param str Pointer to length prefixed string.
|
||||||
|
* @param text Text to append.
|
||||||
|
* @return 'false' on out of memory, previous value will remain intact.
|
||||||
|
* 'true' on success, '*str' may change.
|
||||||
|
*/
|
||||||
|
bool sc_str_append(char **str, const char *text);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param str Pointer to length prefixed string. (char**).
|
||||||
|
* @param fmt Format
|
||||||
|
* @param ... Arguments
|
||||||
|
* @return 'false' on out of memory, previous value will remain intact.
|
||||||
|
* 'true' on success, '*str' may change.
|
||||||
|
*/
|
||||||
|
#define sc_str_append_fmt(str, fmt, ...) \
|
||||||
|
sc_str_set_fmt(str, "%s" fmt, *str, __VA_ARGS__)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compare two length prefixed string. If you want to compare with regular
|
||||||
|
* C string, use strcmp().
|
||||||
|
*
|
||||||
|
* @param str Length prefixed string, must not be NULL.
|
||||||
|
* @param other Length prefixed string, must not be NULL.
|
||||||
|
* @return 'true' if equals.
|
||||||
|
*/
|
||||||
|
bool sc_str_cmp(const char *str, const char *other);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tokenization is zero-copy but a bit tricky. This function will mutate 'str',
|
||||||
|
* but it is temporary. On each 'sc_str_token_begin' call, this function will
|
||||||
|
* place '\0' character at the end of a token and put delimiter at the end of
|
||||||
|
* the 'str'.
|
||||||
|
* e.g user1,user2\0 after first iteration will be user1\0user2,
|
||||||
|
*
|
||||||
|
* sc_str_token_end() will fix original string if necessary.
|
||||||
|
*
|
||||||
|
* usage:
|
||||||
|
*
|
||||||
|
* char *str = sc_str_create("user1,user2,user3");
|
||||||
|
* char *save = NULL; // Must be initialized with NULL.
|
||||||
|
* const char *token;
|
||||||
|
*
|
||||||
|
* while ((token = sc_str_token_begin(str, &save, ",") != NULL) {
|
||||||
|
* printf("token : %s \n", token);
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* sc_str_token_end(str, &save);
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param str Length prefixed string, must not be NULL.
|
||||||
|
* @param save Helper variable for tokenizer code.
|
||||||
|
* @param delim Delimiter list.
|
||||||
|
* @return Token.
|
||||||
|
*/
|
||||||
|
const char *sc_str_token_begin(char *str, char **save, const char *delim);
|
||||||
|
void sc_str_token_end(char *str, char **save);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param str Length prefixed string, must not be NULL.
|
||||||
|
* @param list Character list to trim.
|
||||||
|
* @return 'false' on out of memory, previous value will remain intact.
|
||||||
|
* 'true' on success, '*str' may change.
|
||||||
|
*/
|
||||||
|
bool sc_str_trim(char **str, char *list);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param str Length prefixed string, must not be NULL.
|
||||||
|
* @param start Start index.
|
||||||
|
* @param end End index.
|
||||||
|
* @return 'false' on out of range.
|
||||||
|
* 'false' on out of memory, previous value will remain intact.
|
||||||
|
* 'true' on success, '*str' may change.
|
||||||
|
*/
|
||||||
|
bool sc_str_substring(char **str, uint32_t start, uint32_t end);
|
||||||
|
|
||||||
|
bool sc_str_replace(char **str, const char *rep, const char *with);
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
25
string/str_example.c
Normal file
25
string/str_example.c
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
#include "sc_str.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
char* s1;
|
||||||
|
|
||||||
|
s1 = sc_str_create("**hello**");
|
||||||
|
printf("%s \n", s1); // prints **hello**
|
||||||
|
|
||||||
|
sc_str_append_fmt(&s1, " %s", "world--");
|
||||||
|
printf("%s \n", s1); // prints **hello**world--
|
||||||
|
|
||||||
|
sc_str_trim(&s1, "*-");
|
||||||
|
printf("%s \n", s1); // prints **hello**world--
|
||||||
|
|
||||||
|
sc_str_substring(&s1, 6, 11);
|
||||||
|
printf("%s \n", s1); // world
|
||||||
|
|
||||||
|
sc_str_destroy(s1);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
356
string/str_test.c
Normal file
356
string/str_test.c
Normal file
@ -0,0 +1,356 @@
|
|||||||
|
#include "sc_str.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#ifdef SC_HAVE_WRAP
|
||||||
|
|
||||||
|
bool fail_malloc = false;
|
||||||
|
void *__real_malloc(size_t n);
|
||||||
|
void *__wrap_malloc(size_t n)
|
||||||
|
{
|
||||||
|
if (fail_malloc) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return __real_malloc(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fail_realloc = false;
|
||||||
|
void *__real_realloc(void *p, size_t size);
|
||||||
|
void *__wrap_realloc(void *p, size_t n)
|
||||||
|
{
|
||||||
|
if (fail_realloc) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return __real_realloc(p, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fail_vsnprintf;
|
||||||
|
int fail_vsnprintf_at = -1;
|
||||||
|
extern int __real_vsnprintf(char *str, size_t size, const char *format,
|
||||||
|
va_list ap);
|
||||||
|
int __wrap_vsnprintf(char *str, size_t size, const char *format, va_list ap)
|
||||||
|
{
|
||||||
|
fail_vsnprintf_at--;
|
||||||
|
if (!fail_vsnprintf && (fail_vsnprintf_at) != 0) {
|
||||||
|
return __real_vsnprintf(str, size, format, ap);
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void test1()
|
||||||
|
{
|
||||||
|
assert(sc_str_len(NULL) == -1);
|
||||||
|
sc_str_destroy(NULL);
|
||||||
|
assert(sc_str_dup(NULL) == NULL);
|
||||||
|
|
||||||
|
char *s1 = sc_str_create("test");
|
||||||
|
char *s2 = sc_str_create("test");
|
||||||
|
assert(sc_str_len(s1) == 4);
|
||||||
|
assert(sc_str_cmp(s1, s2));
|
||||||
|
sc_str_set(&s2, "tett");
|
||||||
|
assert(sc_str_cmp(s1, s2) == false);
|
||||||
|
assert(sc_str_set_fmt(&s1, "test%d", 3) == true);
|
||||||
|
assert(strcmp(s1, "test3") == 0);
|
||||||
|
fail_malloc = true;
|
||||||
|
assert(sc_str_set_fmt(&s1, "test%d", 5) == false);
|
||||||
|
assert(strcmp(s1, "test3") == 0);
|
||||||
|
fail_malloc = false;
|
||||||
|
|
||||||
|
sc_str_destroy(s1);
|
||||||
|
sc_str_destroy(s2);
|
||||||
|
|
||||||
|
fail_malloc = true;
|
||||||
|
assert(sc_str_create("test") == NULL);
|
||||||
|
fail_malloc = false;
|
||||||
|
|
||||||
|
s1 = malloc(SC_SIZE_MAX + 2);
|
||||||
|
memset(s1, 'c', SC_SIZE_MAX + 1);
|
||||||
|
s1[SC_SIZE_MAX + 1] = '\0';
|
||||||
|
assert(sc_str_create(s1) == NULL);
|
||||||
|
free(s1);
|
||||||
|
|
||||||
|
s1 = sc_str_create_fmt("%dtest%d", 5, 5);
|
||||||
|
assert(strcmp(s1, "5test5") == 0);
|
||||||
|
s2 = sc_str_dup(s1);
|
||||||
|
assert(sc_str_cmp(s1, s2) == true);
|
||||||
|
sc_str_destroy(s1);
|
||||||
|
sc_str_destroy(s2);
|
||||||
|
|
||||||
|
fail_malloc = true;
|
||||||
|
s1 = sc_str_create_fmt("%dtest%d", 5, 5);
|
||||||
|
assert(s1 == NULL);
|
||||||
|
fail_malloc = false;
|
||||||
|
|
||||||
|
s1 = sc_str_create_len("test", 4);
|
||||||
|
assert(strcmp(s1, "test") == 0);
|
||||||
|
assert(sc_str_len(s1) == 4);
|
||||||
|
assert(sc_str_set(&s1, "testtest") == true);
|
||||||
|
assert(strcmp(s1, "testtest") == 0);
|
||||||
|
assert(sc_str_len(s1) == 8);
|
||||||
|
fail_malloc = true;
|
||||||
|
fail_realloc = true;
|
||||||
|
assert(sc_str_set(&s1, "test") == false);
|
||||||
|
assert(strcmp(s1, "testtest") == 0);
|
||||||
|
assert(sc_str_len(s1) == 8);
|
||||||
|
assert(sc_str_append(&s1, "test") == false);
|
||||||
|
assert(sc_str_append_fmt(&s1, "%s", "test") == false);
|
||||||
|
assert(strcmp(s1, "testtest") == 0);
|
||||||
|
assert(sc_str_len(s1) == 8);
|
||||||
|
fail_malloc = false;
|
||||||
|
fail_realloc = false;
|
||||||
|
|
||||||
|
sc_str_set(&s1, "text");
|
||||||
|
sc_str_append(&s1, "2");
|
||||||
|
sc_str_append_fmt(&s1, "%d", 3);
|
||||||
|
assert(strcmp(s1, "text23") == 0);
|
||||||
|
sc_str_set(&s1, " \n\n;;;;*test ;------;");
|
||||||
|
sc_str_trim(&s1, " \n;*-");
|
||||||
|
assert(strcmp(s1, "test") == 0);
|
||||||
|
sc_str_substring(&s1, 2, 4);
|
||||||
|
assert(strcmp(s1, "st") == 0);
|
||||||
|
assert(sc_str_substring(&s1, 4, 5) == false);
|
||||||
|
assert(sc_str_substring(&s1, 1, 5) == false);
|
||||||
|
|
||||||
|
sc_str_set(&s1, "test");
|
||||||
|
fail_malloc = true;
|
||||||
|
assert(sc_str_substring(&s1, 1, 2) == false);
|
||||||
|
fail_malloc = false;
|
||||||
|
|
||||||
|
fail_vsnprintf = true;
|
||||||
|
assert(sc_str_set_fmt(&s1, "test%d", 3) == false);
|
||||||
|
assert(strcmp(s1, "test") == 0);
|
||||||
|
fail_vsnprintf = false;
|
||||||
|
|
||||||
|
|
||||||
|
fail_vsnprintf_at = 2;
|
||||||
|
s2 = malloc(2000 + 2);
|
||||||
|
memset(s2, 'c', 2000 + 1);
|
||||||
|
s2[2000 + 1] = '\0';
|
||||||
|
assert(sc_str_set_fmt(&s1, "test%s", s2) == false);
|
||||||
|
assert(strcmp(s1, "test") == 0);
|
||||||
|
fail_vsnprintf_at = -1;
|
||||||
|
free(s2);
|
||||||
|
sc_str_destroy(s1);
|
||||||
|
|
||||||
|
fail_malloc = false;
|
||||||
|
fail_realloc = false;
|
||||||
|
fail_vsnprintf = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void test2()
|
||||||
|
{
|
||||||
|
char buf[4000];
|
||||||
|
memset(buf, 'x', 4000);
|
||||||
|
buf[3999] = '\0';
|
||||||
|
|
||||||
|
char *c = sc_str_create("--a**00");
|
||||||
|
fail_malloc = true;
|
||||||
|
assert(sc_str_trim(&c, "-*0") == false);
|
||||||
|
fail_malloc = false;
|
||||||
|
sc_str_trim(&c, "-*0");
|
||||||
|
assert(*c == 'a');
|
||||||
|
sc_str_trim(&c, "a");
|
||||||
|
assert(*c == '\0');
|
||||||
|
|
||||||
|
sc_str_set(&c, "x003");
|
||||||
|
sc_str_trim(&c, "03");
|
||||||
|
assert(strcmp(c, "x") == 0);
|
||||||
|
sc_str_set(&c, "\n\r\nx");
|
||||||
|
sc_str_trim(&c, "\n\r");
|
||||||
|
assert(strcmp(c, "x") == 0);
|
||||||
|
|
||||||
|
sc_str_set(&c, "test****");
|
||||||
|
fail_malloc = true;
|
||||||
|
assert(sc_str_replace(&c, "*", "-"));
|
||||||
|
assert(strcmp(c, "test----") == 0);
|
||||||
|
assert(!sc_str_replace(&c, "-", ""));
|
||||||
|
assert(strcmp(c, "test----") == 0);
|
||||||
|
fail_malloc = false;
|
||||||
|
assert(!sc_str_replace(&c, "----", buf));
|
||||||
|
assert(strcmp(c, "test----") == 0);
|
||||||
|
sc_str_replace(&c, "--", "0");
|
||||||
|
assert(strcmp(c, "test00") == 0);
|
||||||
|
sc_str_destroy(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void test3()
|
||||||
|
{
|
||||||
|
const char *tokens = "token;token;token;token";
|
||||||
|
char *save = NULL;
|
||||||
|
const char *token;
|
||||||
|
char *str = sc_str_create(tokens);
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
while ((token = sc_str_token_begin(str, &save, ";")) != NULL) {
|
||||||
|
assert(strcmp(token, "token") == 0);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(count == 4);
|
||||||
|
assert(strcmp(str, tokens) == 0);
|
||||||
|
assert(sc_str_len(str) == strlen(tokens));
|
||||||
|
|
||||||
|
count = 0;
|
||||||
|
save = NULL;
|
||||||
|
while ((token = sc_str_token_begin(str, &save, ";")) != NULL) {
|
||||||
|
assert(strcmp(token, "token") == 0);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
sc_str_token_end(str, &save);
|
||||||
|
|
||||||
|
assert(count == 4);
|
||||||
|
assert(strcmp(str, tokens) == 0);
|
||||||
|
assert(sc_str_len(str) == strlen(tokens));
|
||||||
|
|
||||||
|
count = 0;
|
||||||
|
save = NULL;
|
||||||
|
while ((token = sc_str_token_begin(str, &save, ";")) != NULL) {
|
||||||
|
assert(strcmp(token, "token") == 0);
|
||||||
|
count++;
|
||||||
|
if (count == 4) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sc_str_token_end(str, &save);
|
||||||
|
|
||||||
|
assert(count == 4);
|
||||||
|
assert(strcmp(str, tokens) == 0);
|
||||||
|
assert(sc_str_len(str) == strlen(tokens));
|
||||||
|
sc_str_destroy(str);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void test4()
|
||||||
|
{
|
||||||
|
char *save = NULL;
|
||||||
|
const char *token;
|
||||||
|
char *str;
|
||||||
|
char list[10][10];
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
str = sc_str_create(";;;");
|
||||||
|
while ((token = sc_str_token_begin(str, &save, ";")) != NULL) {
|
||||||
|
assert(strcmp(token, "") == 0);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
assert(count == 4);
|
||||||
|
|
||||||
|
save = NULL;
|
||||||
|
count = 0;
|
||||||
|
sc_str_set(&str, "tk1;tk2-tk3 tk4 tk5*tk6");
|
||||||
|
while ((token = sc_str_token_begin(str, &save, ";- *")) != NULL) {
|
||||||
|
strcpy(list[count], token);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(count == 7);
|
||||||
|
assert(strcmp(list[0], "tk1") == 0);
|
||||||
|
assert(strcmp(list[1], "tk2") == 0);
|
||||||
|
assert(strcmp(list[2], "tk3") == 0);
|
||||||
|
assert(strcmp(list[3], "tk4") == 0);
|
||||||
|
assert(strcmp(list[4], "") == 0);
|
||||||
|
assert(strcmp(list[5], "tk5") == 0);
|
||||||
|
assert(strcmp(list[6], "tk6") == 0);
|
||||||
|
|
||||||
|
sc_str_token_end(str, &save);
|
||||||
|
|
||||||
|
|
||||||
|
save = NULL;
|
||||||
|
count = 0;
|
||||||
|
sc_str_set(&str, "tk1;tk2-tk3 tk4 tk5*tk6");
|
||||||
|
while ((token = sc_str_token_begin(str, &save, ";- *")) != NULL) {
|
||||||
|
strcpy(list[count], token);
|
||||||
|
count++;
|
||||||
|
if (count == 3) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_str_token_end(str, &save);
|
||||||
|
assert(strcmp(str, "tk1;tk2-tk3 tk4 tk5*tk6") == 0);
|
||||||
|
|
||||||
|
save = NULL;
|
||||||
|
count = 0;
|
||||||
|
sc_str_set(&str, "tk1;tk2-tk3 tk4 tk5*tk6");
|
||||||
|
while ((token = sc_str_token_begin(str, &save, ";- *")) != NULL) {
|
||||||
|
strcpy(list[count], token);
|
||||||
|
count++;
|
||||||
|
if (count == 3) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_str_token_end(str, NULL);
|
||||||
|
assert(strcmp(str, "tk1;tk2-tk3 tk4 tk5*tk6") == 0);
|
||||||
|
|
||||||
|
sc_str_destroy(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test5()
|
||||||
|
{
|
||||||
|
char* s1, *s2;
|
||||||
|
|
||||||
|
s1 = sc_str_create("test");
|
||||||
|
assert(strcmp(s1, "test") == 0);
|
||||||
|
assert(sc_str_len(s1) == 4);
|
||||||
|
s2 = sc_str_dup(s1);
|
||||||
|
assert(strcmp(s2, "test") == 0);
|
||||||
|
assert(sc_str_len(s2) == 4);
|
||||||
|
assert(strcmp(s1, s2) == 0);
|
||||||
|
sc_str_destroy(s2);
|
||||||
|
|
||||||
|
sc_str_set(&s1, "test2");
|
||||||
|
assert(strcmp(s1, "test2") == 0);
|
||||||
|
sc_str_set_fmt(&s1, "test%d", 3);
|
||||||
|
assert(strcmp(s1, "test3") == 0);
|
||||||
|
sc_str_append(&s1, "5");
|
||||||
|
assert(strcmp(s1, "test35") == 0);
|
||||||
|
sc_str_append_fmt(&s1, "%d", 7);
|
||||||
|
assert(strcmp(s1, "test357") == 0);
|
||||||
|
sc_str_substring(&s1, 0, 4);
|
||||||
|
assert(strcmp(s1, "test") == 0);
|
||||||
|
sc_str_trim(&s1, "tes");
|
||||||
|
assert(strcmp(s1, "") == 0);
|
||||||
|
sc_str_set_fmt(&s1, "-;;;- \n \n \n 351234");
|
||||||
|
sc_str_trim(&s1, "-; 34\n");
|
||||||
|
assert(strcmp(s1, "512") == 0);
|
||||||
|
|
||||||
|
sc_str_set(&s1, "test t1t1 test");
|
||||||
|
sc_str_replace(&s1, "t1t1", "-");
|
||||||
|
assert(strcmp(s1, "test - test") == 0);
|
||||||
|
sc_str_replace(&s1, "test", "longer");
|
||||||
|
assert(strcmp(s1, "longer - longer") == 0);
|
||||||
|
sc_str_replace(&s1, "-", "");
|
||||||
|
sc_str_replace(&s1, " ", "");
|
||||||
|
assert(strcmp(s1, "longerlonger") == 0);
|
||||||
|
assert(sc_str_len(s1) == strlen("longerlonger"));
|
||||||
|
assert(sc_str_replace(&s1, "as", "r"));
|
||||||
|
assert(strcmp(s1, "longerlonger") == 0);
|
||||||
|
assert(sc_str_replace(&s1, "r", "R"));
|
||||||
|
assert(strcmp(s1, "longeRlongeR") == 0);
|
||||||
|
assert(sc_str_replace(&s1, "longeR", ""));
|
||||||
|
assert(strcmp(s1, "") == 0);
|
||||||
|
sc_str_destroy(s1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
|
||||||
|
#ifdef SC_HAVE_WRAP
|
||||||
|
test1();
|
||||||
|
test2();
|
||||||
|
#endif
|
||||||
|
test3();
|
||||||
|
test4();
|
||||||
|
test5();
|
||||||
|
return 0;
|
||||||
|
}
|
83
time/CMakeLists.txt
Normal file
83
time/CMakeLists.txt
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.5.1)
|
||||||
|
project(sc_time C)
|
||||||
|
|
||||||
|
set(CMAKE_C_STANDARD 99)
|
||||||
|
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||||
|
set(CMAKE_C_EXTENSIONS OFF)
|
||||||
|
|
||||||
|
add_executable(sc_time time_example.c sc_time.h sc_time.c)
|
||||||
|
|
||||||
|
if (NOT CMAKE_C_COMPILER_ID MATCHES "MSVC")
|
||||||
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -Wall -pedantic -Werror -D_GNU_SOURCE")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# --------------------- Test Configuration Start ---------------------------- #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
|
||||||
|
include(CTest)
|
||||||
|
include(CheckCCompilerFlag)
|
||||||
|
|
||||||
|
enable_testing()
|
||||||
|
|
||||||
|
add_executable(${PROJECT_NAME}_test time_test.c sc_time.c)
|
||||||
|
|
||||||
|
if ("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR
|
||||||
|
"${CMAKE_C_COMPILER_ID}" STREQUAL "AppleClang" OR
|
||||||
|
"${CMAKE_C_COMPILER_ID}" STREQUAL "GNU")
|
||||||
|
|
||||||
|
target_compile_options(${PROJECT_NAME}_test PRIVATE -fno-omit-frame-pointer)
|
||||||
|
|
||||||
|
if (SANITIZER)
|
||||||
|
target_compile_options(${PROJECT_NAME}_test PRIVATE -fsanitize=${SANITIZER})
|
||||||
|
target_link_options(${PROJECT_NAME}_test PRIVATE -fsanitize=${SANITIZER})
|
||||||
|
endif ()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
add_test(NAME ${PROJECT_NAME}_test COMMAND ${PROJECT_NAME}_test)
|
||||||
|
|
||||||
|
SET(MEMORYCHECK_COMMAND_OPTIONS
|
||||||
|
"-q --log-fd=2 --trace-children=yes --track-origins=yes \
|
||||||
|
--leak-check=full --show-leak-kinds=all --show-reachable=yes \
|
||||||
|
--error-exitcode=255")
|
||||||
|
|
||||||
|
add_custom_target(valgrind_${PROJECT_NAME} ${CMAKE_COMMAND}
|
||||||
|
-E env CTEST_OUTPUT_ON_FAILURE=1
|
||||||
|
${CMAKE_CTEST_COMMAND} -C $<CONFIG>
|
||||||
|
--overwrite MemoryCheckCommandOptions=${MEMORYCHECK_COMMAND_OPTIONS}
|
||||||
|
--verbose -T memcheck WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||||
|
|
||||||
|
add_custom_target(check_${PROJECT_NAME} ${CMAKE_COMMAND}
|
||||||
|
-E env CTEST_OUTPUT_ON_FAILURE=1
|
||||||
|
${CMAKE_CTEST_COMMAND} -C $<CONFIG> --verbose
|
||||||
|
WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||||
|
|
||||||
|
# ----------------------- - Code Coverage Start ----------------------------- #
|
||||||
|
|
||||||
|
if (${CMAKE_BUILD_TYPE} MATCHES "Coverage")
|
||||||
|
if ("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU")
|
||||||
|
target_compile_options(${PROJECT_NAME}_test PRIVATE --coverage)
|
||||||
|
target_link_libraries(${PROJECT_NAME}_test gcov)
|
||||||
|
else()
|
||||||
|
message(FATAL_ERROR "Only GCC is supported for coverage")
|
||||||
|
endif()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
add_custom_target(coverage_${PROJECT_NAME})
|
||||||
|
add_custom_command(
|
||||||
|
TARGET coverage_${PROJECT_NAME}
|
||||||
|
COMMAND lcov --capture --directory ..
|
||||||
|
--output-file coverage.info --rc lcov_branch_coverage=1
|
||||||
|
COMMAND lcov --remove coverage.info '/usr/*' '*example*' '*test*'
|
||||||
|
--output-file coverage.info --rc lcov_branch_coverage=1
|
||||||
|
COMMAND lcov --list coverage.info --rc lcov_branch_coverage=1
|
||||||
|
)
|
||||||
|
|
||||||
|
add_dependencies(coverage_${PROJECT_NAME} check_${PROJECT_NAME})
|
||||||
|
|
||||||
|
# -------------------------- Code Coverage End ------------------------------ #
|
||||||
|
|
||||||
|
|
||||||
|
# ----------------------- Test Configuration End ---------------------------- #
|
||||||
|
|
6
time/README.md
Normal file
6
time/README.md
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
# Time and sleep functions
|
||||||
|
|
||||||
|
#### Overview
|
||||||
|
|
||||||
|
- Time and sleep functions for Posix and Windows systems.
|
||||||
|
- Just copy <b>sc_time.h</b> and <b>sc_time.c</b> to your project.
|
147
time/sc_time.c
Normal file
147
time/sc_time.c
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020 Ozan Tezcan
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
#include "sc_time.h"
|
||||||
|
|
||||||
|
#if defined(_WIN32) || defined(_WIN64)
|
||||||
|
#include <assert.h>
|
||||||
|
#include <windows.h>
|
||||||
|
#else
|
||||||
|
#include <assert.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
#include <time.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
uint64_t sc_time_ms()
|
||||||
|
{
|
||||||
|
#if defined(_WIN32) || defined(_WIN64)
|
||||||
|
FILETIME ft;
|
||||||
|
ULARGE_INTEGER dateTime;
|
||||||
|
|
||||||
|
GetSystemTimeAsFileTime(&ft);
|
||||||
|
dateTime.LowPart = ft.dwLowDateTime;
|
||||||
|
dateTime.HighPart = ft.dwHighDateTime;
|
||||||
|
|
||||||
|
return (dateTime.QuadPart / 10000);
|
||||||
|
#else
|
||||||
|
int rc;
|
||||||
|
struct timespec ts;
|
||||||
|
|
||||||
|
rc = clock_gettime(CLOCK_REALTIME, &ts);
|
||||||
|
assert(rc == 0);
|
||||||
|
|
||||||
|
return ts.tv_sec * 1000 + (uint64_t)(ts.tv_nsec / 10e6);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t sc_time_ns()
|
||||||
|
{
|
||||||
|
#if defined(_WIN32) || defined(_WIN64)
|
||||||
|
FILETIME ft;
|
||||||
|
ULARGE_INTEGER dateTime;
|
||||||
|
|
||||||
|
GetSystemTimeAsFileTime(&ft);
|
||||||
|
dateTime.LowPart = ft.dwLowDateTime;
|
||||||
|
dateTime.HighPart = ft.dwHighDateTime;
|
||||||
|
|
||||||
|
return (dateTime.QuadPart * 100);
|
||||||
|
#else
|
||||||
|
int rc;
|
||||||
|
struct timespec ts;
|
||||||
|
|
||||||
|
rc = clock_gettime(CLOCK_REALTIME, &ts);
|
||||||
|
assert(rc == 0);
|
||||||
|
|
||||||
|
return ts.tv_sec * 1000000000 + ts.tv_nsec;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t sc_time_mono_ms()
|
||||||
|
{
|
||||||
|
#if defined(_WIN32) || defined(_WIN64)
|
||||||
|
// System frequency does not change at run-time, cache it
|
||||||
|
static int64_t frequency = 0;
|
||||||
|
if (frequency == 0) {
|
||||||
|
LARGE_INTEGER freq;
|
||||||
|
QueryPerformanceFrequency(&freq);
|
||||||
|
assert(freq.QuadPart != 0);
|
||||||
|
frequency = freq.QuadPart;
|
||||||
|
}
|
||||||
|
LARGE_INTEGER count;
|
||||||
|
QueryPerformanceCounter(&count);
|
||||||
|
return (int64_t)(count.QuadPart * 1000) / frequency;
|
||||||
|
#else
|
||||||
|
int rc;
|
||||||
|
struct timespec ts;
|
||||||
|
|
||||||
|
rc = clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||||
|
assert(rc == 0);
|
||||||
|
|
||||||
|
return (int64_t)((int64_t) ts.tv_sec * 1000 +
|
||||||
|
(int64_t) ts.tv_nsec / 1000000);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t sc_time_mono_ns()
|
||||||
|
{
|
||||||
|
#if defined(_WIN32) || defined(_WIN64)
|
||||||
|
static int64_t frequency = 0;
|
||||||
|
if (frequency == 0) {
|
||||||
|
LARGE_INTEGER freq;
|
||||||
|
QueryPerformanceFrequency(&freq);
|
||||||
|
assert(freq.QuadPart != 0);
|
||||||
|
frequency = freq.QuadPart;
|
||||||
|
}
|
||||||
|
LARGE_INTEGER count;
|
||||||
|
QueryPerformanceCounter(&count);
|
||||||
|
return (uint64_t)(count.QuadPart * 1000000000) / frequency;
|
||||||
|
#else
|
||||||
|
int rc;
|
||||||
|
struct timespec ts;
|
||||||
|
|
||||||
|
rc = clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||||
|
assert(rc == 0);
|
||||||
|
|
||||||
|
return ((uint64_t) ts.tv_sec * 1000000000 + (uint64_t) ts.tv_nsec);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void sc_time_sleep(uint64_t milliseconds)
|
||||||
|
{
|
||||||
|
#if defined(_WIN32) || defined(_WIN64)
|
||||||
|
Sleep(milliseconds);
|
||||||
|
#else
|
||||||
|
int rc;
|
||||||
|
struct timespec t;
|
||||||
|
|
||||||
|
t.tv_sec = milliseconds / 1000;
|
||||||
|
t.tv_nsec = (milliseconds % 1000) * 1000000;
|
||||||
|
|
||||||
|
do {
|
||||||
|
rc = nanosleep(&t, NULL);
|
||||||
|
} while (rc != 0 && errno != EINTR);
|
||||||
|
|
||||||
|
assert(rc == 0);
|
||||||
|
#endif
|
||||||
|
}
|
58
time/sc_time.h
Normal file
58
time/sc_time.h
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020 Ozan Tezcan
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
#ifndef SC_TIME_H
|
||||||
|
#define SC_TIME_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is not a monotonic timer.
|
||||||
|
* @return Current timestamp in milliseconds.
|
||||||
|
*/
|
||||||
|
uint64_t sc_time_ms();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is not a monotonic timer.
|
||||||
|
* @return Current timestamp in nanoseconds.
|
||||||
|
*/
|
||||||
|
uint64_t sc_time_ns();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Monotonic timer.
|
||||||
|
* @return Current timestamp in milliseconds.
|
||||||
|
*/
|
||||||
|
uint64_t sc_time_mono_ms();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Monotonic timer.
|
||||||
|
* @return Current timestamp in nanoseconds.
|
||||||
|
*/
|
||||||
|
uint64_t sc_time_mono_ns();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param milliseconds Milliseconds to sleep.
|
||||||
|
*/
|
||||||
|
void sc_time_sleep(uint64_t milliseconds);
|
||||||
|
|
||||||
|
#endif
|
11
time/time_example.c
Normal file
11
time/time_example.c
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
#include "sc_time.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
uint64_t t = sc_time_ms();
|
||||||
|
sc_time_sleep(1000);
|
||||||
|
printf("%lu \n", (unsigned long) (sc_time_ms() - t));
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
47
time/time_test.c
Normal file
47
time/time_test.c
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
#include "sc_time.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
void test1(void)
|
||||||
|
{
|
||||||
|
assert(sc_time_ns() != 0);
|
||||||
|
assert(sc_time_ms() != 0);
|
||||||
|
|
||||||
|
for (int i = 0; i < 100000; i++) {
|
||||||
|
uint64_t t1 = sc_time_mono_ms();
|
||||||
|
uint64_t t2 = sc_time_mono_ms();
|
||||||
|
|
||||||
|
assert(t2 >= t1);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < 100000; i++) {
|
||||||
|
uint64_t t1 = sc_time_mono_ns();
|
||||||
|
uint64_t t2 = sc_time_mono_ns();
|
||||||
|
|
||||||
|
assert(t2 >= t1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void test2(void)
|
||||||
|
{
|
||||||
|
uint64_t t1, t2;
|
||||||
|
|
||||||
|
t1 = sc_time_mono_ms();
|
||||||
|
sc_time_sleep(1000);
|
||||||
|
t2 = sc_time_mono_ms();
|
||||||
|
|
||||||
|
assert(t2 > t1);
|
||||||
|
|
||||||
|
t1 = sc_time_mono_ns();
|
||||||
|
sc_time_sleep(1000);
|
||||||
|
t2 = sc_time_mono_ns();
|
||||||
|
|
||||||
|
assert(t2 > t1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
test1();
|
||||||
|
test2();
|
||||||
|
return 0;
|
||||||
|
}
|
96
timer/CMakeLists.txt
Normal file
96
timer/CMakeLists.txt
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.5.1)
|
||||||
|
project(sc_timer C)
|
||||||
|
|
||||||
|
set(CMAKE_C_STANDARD 99)
|
||||||
|
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||||
|
set(CMAKE_C_EXTENSIONS OFF)
|
||||||
|
|
||||||
|
add_executable(sc_timer timer_example.c sc_timer.h sc_timer.c)
|
||||||
|
|
||||||
|
if (NOT CMAKE_C_COMPILER_ID MATCHES "MSVC")
|
||||||
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fno-omit-frame-pointer -g -pedantic -Werror -D_GNU_SOURCE")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# --------------------- Test Configuration Start ---------------------------- #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
|
||||||
|
include(CTest)
|
||||||
|
include(CheckCCompilerFlag)
|
||||||
|
|
||||||
|
enable_testing()
|
||||||
|
|
||||||
|
add_executable(${PROJECT_NAME}_test timer_test.c sc_timer.c)
|
||||||
|
|
||||||
|
target_compile_options(${PROJECT_NAME}_test PRIVATE -DSC_SIZE_MAX=1400000ul)
|
||||||
|
|
||||||
|
if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||||
|
if ("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR
|
||||||
|
"${CMAKE_C_COMPILER_ID}" STREQUAL "GNU")
|
||||||
|
|
||||||
|
target_compile_options(${PROJECT_NAME}_test PRIVATE -DSC_HAVE_WRAP)
|
||||||
|
target_compile_options(${PROJECT_NAME}_test PRIVATE -fno-builtin)
|
||||||
|
target_link_options(${PROJECT_NAME}_test PRIVATE -Wl,--wrap=malloc)
|
||||||
|
|
||||||
|
endif ()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
if ("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR
|
||||||
|
"${CMAKE_C_COMPILER_ID}" STREQUAL "AppleClang" OR
|
||||||
|
"${CMAKE_C_COMPILER_ID}" STREQUAL "GNU")
|
||||||
|
|
||||||
|
target_compile_options(${PROJECT_NAME}_test PRIVATE -fno-omit-frame-pointer)
|
||||||
|
|
||||||
|
if (SANITIZER)
|
||||||
|
target_compile_options(${PROJECT_NAME}_test PRIVATE -fsanitize=${SANITIZER})
|
||||||
|
target_link_options(${PROJECT_NAME}_test PRIVATE -fsanitize=${SANITIZER})
|
||||||
|
endif ()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
add_test(NAME ${PROJECT_NAME}_test COMMAND ${PROJECT_NAME}_test)
|
||||||
|
|
||||||
|
SET(MEMORYCHECK_COMMAND_OPTIONS
|
||||||
|
"-q --log-fd=2 --trace-children=yes --track-origins=yes \
|
||||||
|
--leak-check=full --show-leak-kinds=all --show-reachable=yes \
|
||||||
|
--error-exitcode=255")
|
||||||
|
|
||||||
|
add_custom_target(valgrind_${PROJECT_NAME} ${CMAKE_COMMAND}
|
||||||
|
-E env CTEST_OUTPUT_ON_FAILURE=1
|
||||||
|
${CMAKE_CTEST_COMMAND} -C $<CONFIG>
|
||||||
|
--overwrite MemoryCheckCommandOptions=${MEMORYCHECK_COMMAND_OPTIONS}
|
||||||
|
--verbose -T memcheck WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||||
|
|
||||||
|
add_custom_target(check_${PROJECT_NAME} ${CMAKE_COMMAND}
|
||||||
|
-E env CTEST_OUTPUT_ON_FAILURE=1
|
||||||
|
${CMAKE_CTEST_COMMAND} -C $<CONFIG> --verbose
|
||||||
|
WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||||
|
|
||||||
|
# ----------------------- - Code Coverage Start ----------------------------- #
|
||||||
|
|
||||||
|
if (${CMAKE_BUILD_TYPE} MATCHES "Coverage")
|
||||||
|
if ("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU")
|
||||||
|
target_compile_options(${PROJECT_NAME}_test PRIVATE --coverage)
|
||||||
|
target_link_libraries(${PROJECT_NAME}_test gcov)
|
||||||
|
else()
|
||||||
|
message(FATAL_ERROR "Only GCC is supported for coverage")
|
||||||
|
endif()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
add_custom_target(coverage_${PROJECT_NAME})
|
||||||
|
add_custom_command(
|
||||||
|
TARGET coverage_${PROJECT_NAME}
|
||||||
|
COMMAND lcov --capture --directory ..
|
||||||
|
--output-file coverage.info --rc lcov_branch_coverage=1
|
||||||
|
COMMAND lcov --remove coverage.info '/usr/*' '*example*' '*test*'
|
||||||
|
--output-file coverage.info --rc lcov_branch_coverage=1
|
||||||
|
COMMAND lcov --list coverage.info --rc lcov_branch_coverage=1
|
||||||
|
)
|
||||||
|
|
||||||
|
add_dependencies(coverage_${PROJECT_NAME} check_${PROJECT_NAME})
|
||||||
|
|
||||||
|
# -------------------------- Code Coverage End ------------------------------ #
|
||||||
|
|
||||||
|
|
||||||
|
# ----------------------- Test Configuration End ---------------------------- #
|
||||||
|
|
110
timer/README.md
Normal file
110
timer/README.md
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
# Timer
|
||||||
|
|
||||||
|
#### Overview
|
||||||
|
|
||||||
|
- Hashed timer wheel implementation.
|
||||||
|
- Provides fast cancel and poll operations compared to a priority queue.
|
||||||
|
- Timers in the same hash slot are not ordered between each other. So, basically
|
||||||
|
this data structure trades accuracy for performance. Schedule a timer for
|
||||||
|
10000ms and another for 10001ms and you might see 10001ms timer expires
|
||||||
|
just before 10000ms timer.
|
||||||
|
- Just copy <b>sc_timer.h</b> and <b>sc_timer.c</b> to your project.
|
||||||
|
|
||||||
|
|
||||||
|
##### Usage
|
||||||
|
|
||||||
|
|
||||||
|
```c
|
||||||
|
#include "sc_timer.h"
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
uint64_t time_ms();
|
||||||
|
void sleep_ms(uint64_t milliseconds);
|
||||||
|
|
||||||
|
|
||||||
|
void callback(void *arg, uint64_t timeout, void *data)
|
||||||
|
{
|
||||||
|
struct sc_timer *timer = arg;
|
||||||
|
char *timer_name = data;
|
||||||
|
|
||||||
|
printf("timeout : %zu, data : %s \n", timeout, timer_name);
|
||||||
|
// Schedule back
|
||||||
|
sc_timer_add(timer, "timer1", 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
uint64_t next_timeout;
|
||||||
|
struct sc_timer timer;
|
||||||
|
|
||||||
|
sc_timer_init(&timer, time_ms());
|
||||||
|
sc_timer_add(&timer, "timer1", 1000);
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
next_timeout = sc_timer_timeout(&timer, time_ms(), &timer, callback);
|
||||||
|
sleep_ms(next_timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* sleep() and time() functions for Windows and Posix.
|
||||||
|
* These functions are here just to make this example run on your platform.
|
||||||
|
*
|
||||||
|
* Use your monotonic timer and sleep function of your target platform.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if defined(_WIN32) || defined(_WIN64)
|
||||||
|
#include <windows.h>
|
||||||
|
#else
|
||||||
|
#include <sys/time.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
uint64_t time_ms()
|
||||||
|
{
|
||||||
|
#if defined(_WIN32) || defined(_WIN64)
|
||||||
|
// System frequency does not change at run-time, cache it
|
||||||
|
static int64_t frequency = 0;
|
||||||
|
if (frequency == 0) {
|
||||||
|
LARGE_INTEGER freq;
|
||||||
|
QueryPerformanceFrequency(&freq);
|
||||||
|
assert(freq.QuadPart != 0);
|
||||||
|
frequency = freq.QuadPart;
|
||||||
|
}
|
||||||
|
LARGE_INTEGER count;
|
||||||
|
QueryPerformanceCounter(&count);
|
||||||
|
return (int64_t)(count.QuadPart * 1000) / frequency;
|
||||||
|
#else
|
||||||
|
struct timespec ts;
|
||||||
|
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||||
|
|
||||||
|
return (int64_t)((int64_t) ts.tv_sec * 1000 +
|
||||||
|
(int64_t) ts.tv_nsec / 1000000);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void sleep_ms(uint64_t milliseconds)
|
||||||
|
{
|
||||||
|
#if defined(_WIN32) || defined(_WIN64)
|
||||||
|
Sleep(milliseconds);
|
||||||
|
#else
|
||||||
|
int rc;
|
||||||
|
struct timespec t;
|
||||||
|
|
||||||
|
t.tv_sec = milliseconds / 1000;
|
||||||
|
t.tv_nsec = (milliseconds % 1000) * 1000000;
|
||||||
|
|
||||||
|
do {
|
||||||
|
rc = nanosleep(&t, NULL);
|
||||||
|
} while (rc != 0 && errno != EINTR);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
214
timer/sc_timer.c
Normal file
214
timer/sc_timer.c
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020 Ozan Tezcan
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "sc_timer.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <memory.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#define TICK 16u
|
||||||
|
#define WHEEL_COUNT 16u
|
||||||
|
|
||||||
|
#ifndef SC_SIZE_MAX
|
||||||
|
#define SC_SIZE_MAX UINT32_MAX
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define SC_CAP_MAX (SC_SIZE_MAX / sizeof(struct sc_timer_data)) / WHEEL_COUNT
|
||||||
|
|
||||||
|
bool sc_timer_init(struct sc_timer *timer, uint64_t timestamp)
|
||||||
|
{
|
||||||
|
const size_t wheel_cap = 4;
|
||||||
|
const size_t cap = WHEEL_COUNT * wheel_cap;
|
||||||
|
const size_t size = cap * sizeof(struct sc_timer_data);
|
||||||
|
|
||||||
|
timer->count = 0;
|
||||||
|
timer->head = 0;
|
||||||
|
timer->wheel = wheel_cap;
|
||||||
|
timer->timestamp = timestamp;
|
||||||
|
|
||||||
|
timer->list = sc_timer_malloc(size);
|
||||||
|
if (timer->list == NULL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < cap; i++) {
|
||||||
|
timer->list[i].timeout = UINT64_MAX;
|
||||||
|
timer->list[i].data = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sc_timer_term(struct sc_timer *timer)
|
||||||
|
{
|
||||||
|
sc_timer_free(timer->list);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sc_timer_clear(struct sc_timer *timer)
|
||||||
|
{
|
||||||
|
const size_t cap = timer->wheel * WHEEL_COUNT;
|
||||||
|
|
||||||
|
timer->count = 0;
|
||||||
|
timer->head = 0;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < cap; i++) {
|
||||||
|
timer->list[i].timeout = UINT64_MAX;
|
||||||
|
timer->list[i].data = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool expand(struct sc_timer *timer)
|
||||||
|
{
|
||||||
|
size_t cap = timer->wheel * WHEEL_COUNT * 2;
|
||||||
|
size_t size = cap * sizeof(struct sc_timer_data);
|
||||||
|
struct sc_timer_data *alloc;
|
||||||
|
|
||||||
|
// Check overflow
|
||||||
|
if (timer->wheel > SC_CAP_MAX / 2) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
alloc = sc_timer_malloc(size);
|
||||||
|
if (alloc == NULL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < cap; i++) {
|
||||||
|
alloc[i].timeout = UINT64_MAX;
|
||||||
|
alloc[i].data = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy from old list to new list
|
||||||
|
for (uint32_t i = 0; i < WHEEL_COUNT; i++) {
|
||||||
|
void *dest = &alloc[(i * timer->wheel * 2)];
|
||||||
|
void *src = &timer->list[(i * timer->wheel)];
|
||||||
|
size_t copy = sizeof(struct sc_timer_data) * timer->wheel;
|
||||||
|
|
||||||
|
memcpy(dest, src, copy);
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_timer_free(timer->list);
|
||||||
|
|
||||||
|
timer->list = alloc;
|
||||||
|
timer->wheel *= 2;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t sc_timer_add(struct sc_timer *timer, void *data, uint64_t timeout)
|
||||||
|
{
|
||||||
|
const size_t pos = (timeout / TICK + timer->head) & (WHEEL_COUNT - 1);
|
||||||
|
uint64_t id;
|
||||||
|
size_t seq, index, wheel_pos;
|
||||||
|
|
||||||
|
assert(timeout < UINT64_MAX);
|
||||||
|
|
||||||
|
timer->count++;
|
||||||
|
|
||||||
|
wheel_pos = (pos * timer->wheel);
|
||||||
|
for (seq = 0; seq < timer->wheel; seq++) {
|
||||||
|
index = wheel_pos + seq;
|
||||||
|
if (timer->list[index].timeout == UINT64_MAX) {
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!expand(timer)) {
|
||||||
|
return SC_TIMER_INVALID;
|
||||||
|
}
|
||||||
|
|
||||||
|
index = (pos * timer->wheel) + (seq);
|
||||||
|
assert(timer->list[index].timeout == UINT64_MAX);
|
||||||
|
|
||||||
|
out:
|
||||||
|
timer->list[index].timeout = timeout + timer->timestamp;
|
||||||
|
timer->list[index].data = data;
|
||||||
|
|
||||||
|
id = (((uint64_t) seq) << 32u) | pos;
|
||||||
|
assert(id != SC_TIMER_INVALID);
|
||||||
|
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sc_timer_cancel(struct sc_timer *timer, uint64_t *id)
|
||||||
|
{
|
||||||
|
size_t pos;
|
||||||
|
|
||||||
|
if (*id == SC_TIMER_INVALID) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
timer->count--;
|
||||||
|
pos = (((uint32_t) *id) * timer->wheel) + (*id >> 32u);
|
||||||
|
|
||||||
|
assert(timer->list[pos].timeout != UINT64_MAX);
|
||||||
|
timer->list[pos].timeout = UINT64_MAX;
|
||||||
|
*id = SC_TIMER_INVALID;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t sc_timer_timeout(struct sc_timer *timer, uint64_t timestamp, void *arg,
|
||||||
|
void (*callback)(void *, uint64_t, void *))
|
||||||
|
{
|
||||||
|
#define min(a, b) (a) < (b) ? (a) : (b)
|
||||||
|
|
||||||
|
const uint64_t time = timestamp - timer->timestamp;
|
||||||
|
uint32_t wheel, base;
|
||||||
|
uint32_t head = timer->head;
|
||||||
|
uint32_t wheels = min(time / TICK, WHEEL_COUNT);
|
||||||
|
|
||||||
|
if (wheels == 0) {
|
||||||
|
return min(TICK - time, TICK);
|
||||||
|
}
|
||||||
|
|
||||||
|
timer->timestamp = timestamp;
|
||||||
|
timer->head = (timer->head + wheels) & (WHEEL_COUNT - 1);
|
||||||
|
|
||||||
|
while (wheels-- > 0) {
|
||||||
|
wheel = timer->wheel;
|
||||||
|
base = wheel * head;
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < wheel; i++) {
|
||||||
|
struct sc_timer_data *item = &timer->list[base + i];
|
||||||
|
|
||||||
|
if (item->timeout <= timer->timestamp) {
|
||||||
|
uint64_t timeout = item->timeout;
|
||||||
|
item->timeout = UINT64_MAX;
|
||||||
|
|
||||||
|
timer->count--;
|
||||||
|
callback(arg, timeout, item->data);
|
||||||
|
|
||||||
|
// Recalculates position each time because there might be newly
|
||||||
|
// added timers in the callback and it might require expansion
|
||||||
|
// of the list.
|
||||||
|
base = timer->wheel * head;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
head = (head + 1) & (WHEEL_COUNT - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return min(TICK - time, TICK);
|
||||||
|
}
|
||||||
|
|
125
timer/sc_timer.h
Normal file
125
timer/sc_timer.h
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020 Ozan Tezcan
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef SC_TIMER_H
|
||||||
|
#define SC_TIMER_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#define SC_TIMER_INVALID UINT64_MAX
|
||||||
|
|
||||||
|
struct sc_timer_data
|
||||||
|
{
|
||||||
|
uint64_t timeout;
|
||||||
|
void *data;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct sc_timer
|
||||||
|
{
|
||||||
|
uint64_t timestamp;
|
||||||
|
size_t head;
|
||||||
|
size_t wheel;
|
||||||
|
size_t count;
|
||||||
|
struct sc_timer_data *list;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define sc_timer_malloc malloc
|
||||||
|
#define sc_timer_free free
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param timer Timer
|
||||||
|
* @param timestamp Current timestamp. Use monotonic timer source.
|
||||||
|
* @return 'false' on out of memory.
|
||||||
|
*/
|
||||||
|
bool sc_timer_init(struct sc_timer *timer, uint64_t timestamp);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy timer.
|
||||||
|
* @param timer Timer
|
||||||
|
*/
|
||||||
|
void sc_timer_term(struct sc_timer *timer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove all timers without deallocating underlying memory.
|
||||||
|
* @param timer
|
||||||
|
*/
|
||||||
|
void sc_timer_clear(struct sc_timer *timer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add timer
|
||||||
|
* 'timeout' is relative to latest 'timestamp' value given to 'timer' object.
|
||||||
|
*
|
||||||
|
* e.g sc_timer_init(&timer, 1000); // Current timestamp is 1000.
|
||||||
|
* sc_timer_add(&timer, arg, 10); // Timeout will be at 1010.
|
||||||
|
* sc_timer_timeout(&timer, 2000, arg, callback); // Timestamp is now 2000.
|
||||||
|
* sc_timer_add(&timer, arg, 10); // Timeout will be at 2010.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param timer Timer
|
||||||
|
* @param data User data to pass into callback on 'sc_timer_timeout' call.
|
||||||
|
* @param timeout Timeout value, this is relative to 'sc_timer_init's timer.
|
||||||
|
* e.g sc_timer_init(&timer, 10); // say, start time 10 milliseconds
|
||||||
|
* @return SC_TIMER_INVALID on out of memory. Otherwise, timer id. You
|
||||||
|
* can cancel this timer via this id.
|
||||||
|
*/
|
||||||
|
uint64_t sc_timer_add(struct sc_timer *timer, void *data, uint64_t timeout);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* uint64_t id = sc_timer_add(&timer, arg, 10);
|
||||||
|
* sc_timer_cancel(&timer, &id);
|
||||||
|
*
|
||||||
|
* @param timer Timer
|
||||||
|
* @param id Timer id
|
||||||
|
*/
|
||||||
|
void sc_timer_cancel(struct sc_timer *timer, uint64_t *id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logical pattern is :
|
||||||
|
*
|
||||||
|
* e.g:
|
||||||
|
* struct sc_timer timer;
|
||||||
|
* sc_timer_init(&timer, time_ms());
|
||||||
|
* sc_timer_add(&timer, data, 100);
|
||||||
|
*
|
||||||
|
* while (true) {
|
||||||
|
* uint64_t timeout = sc_timer_timeout(&timer, time_ms(), arg, callback);
|
||||||
|
* sleep(timeout); // select(timeout), epoll_wait(timeout) etc..
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param timer Timer
|
||||||
|
* @param timestamp Current timestamp
|
||||||
|
* @param arg User data to user callback
|
||||||
|
* @param callback 'arg' is user data.
|
||||||
|
* 'timeout' is scheduled timeout for that timer.
|
||||||
|
* 'data' is what user passed on 'sc_timer_add'.
|
||||||
|
* @return next timeout.
|
||||||
|
*/
|
||||||
|
uint64_t sc_timer_timeout(struct sc_timer *timer, uint64_t timestamp, void *arg,
|
||||||
|
void (*callback)(void *arg, uint64_t timeout,
|
||||||
|
void *data));
|
||||||
|
#endif
|
82
timer/timer_example.c
Normal file
82
timer/timer_example.c
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
#include "sc_timer.h"
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
uint64_t time_ms();
|
||||||
|
void sleep_ms(uint64_t milliseconds);
|
||||||
|
|
||||||
|
|
||||||
|
void callback(void *arg, uint64_t timeout, void *data)
|
||||||
|
{
|
||||||
|
struct sc_timer *timer = arg;
|
||||||
|
char *timer_name = data;
|
||||||
|
|
||||||
|
printf("timeout : %lu, data : %s \n", (unsigned long) timeout, timer_name);
|
||||||
|
// Schedule back
|
||||||
|
sc_timer_add(timer, "timer1", 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
uint64_t next_timeout;
|
||||||
|
struct sc_timer timer;
|
||||||
|
|
||||||
|
sc_timer_init(&timer, time_ms());
|
||||||
|
sc_timer_add(&timer, "timer1", 1000);
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
next_timeout = sc_timer_timeout(&timer, time_ms(), &timer, callback);
|
||||||
|
sleep_ms(next_timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#if defined(_WIN32) || defined(_WIN64)
|
||||||
|
#include <windows.h>
|
||||||
|
#else
|
||||||
|
#include <sys/time.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
uint64_t time_ms()
|
||||||
|
{
|
||||||
|
#if defined(_WIN32) || defined(_WIN64)
|
||||||
|
// System frequency does not change at run-time, cache it
|
||||||
|
static int64_t frequency = 0;
|
||||||
|
if (frequency == 0) {
|
||||||
|
LARGE_INTEGER freq;
|
||||||
|
QueryPerformanceFrequency(&freq);
|
||||||
|
frequency = freq.QuadPart;
|
||||||
|
}
|
||||||
|
LARGE_INTEGER count;
|
||||||
|
QueryPerformanceCounter(&count);
|
||||||
|
return (int64_t)(count.QuadPart * 1000) / frequency;
|
||||||
|
#else
|
||||||
|
struct timespec ts;
|
||||||
|
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||||
|
|
||||||
|
return (int64_t)((int64_t) ts.tv_sec * 1000 +
|
||||||
|
(int64_t) ts.tv_nsec / 1000000);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void sleep_ms(uint64_t milliseconds)
|
||||||
|
{
|
||||||
|
#if defined(_WIN32) || defined(_WIN64)
|
||||||
|
Sleep(milliseconds);
|
||||||
|
#else
|
||||||
|
int rc;
|
||||||
|
struct timespec t;
|
||||||
|
|
||||||
|
t.tv_sec = milliseconds / 1000;
|
||||||
|
t.tv_nsec = (milliseconds % 1000) * 1000000;
|
||||||
|
|
||||||
|
do {
|
||||||
|
rc = nanosleep(&t, NULL);
|
||||||
|
} while (rc != 0 && errno != EINTR);
|
||||||
|
#endif
|
||||||
|
}
|
257
timer/timer_test.c
Normal file
257
timer/timer_test.c
Normal file
@ -0,0 +1,257 @@
|
|||||||
|
#include "sc_timer.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
#if defined(_WIN32) || defined(_WIN64)
|
||||||
|
#include <windows.h>
|
||||||
|
#else
|
||||||
|
#include <sys/time.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
uint64_t time_ms()
|
||||||
|
{
|
||||||
|
#if defined(_WIN32) || defined(_WIN64)
|
||||||
|
// System frequency does not change at run-time, cache it
|
||||||
|
static int64_t frequency = 0;
|
||||||
|
if (frequency == 0) {
|
||||||
|
LARGE_INTEGER freq;
|
||||||
|
QueryPerformanceFrequency(&freq);
|
||||||
|
assert(freq.QuadPart != 0);
|
||||||
|
frequency = freq.QuadPart;
|
||||||
|
}
|
||||||
|
LARGE_INTEGER count;
|
||||||
|
QueryPerformanceCounter(&count);
|
||||||
|
return (int64_t)(count.QuadPart * 1000) / frequency;
|
||||||
|
#else
|
||||||
|
int rc;
|
||||||
|
struct timespec ts;
|
||||||
|
|
||||||
|
rc = clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||||
|
assert(rc == 0);
|
||||||
|
|
||||||
|
return (int64_t)((int64_t) ts.tv_sec * 1000 +
|
||||||
|
(int64_t) ts.tv_nsec / 1000000);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void sleep_ms(uint64_t milliseconds)
|
||||||
|
{
|
||||||
|
#if defined(_WIN32) || defined(_WIN64)
|
||||||
|
Sleep(milliseconds);
|
||||||
|
#else
|
||||||
|
int rc;
|
||||||
|
struct timespec t;
|
||||||
|
|
||||||
|
t.tv_sec = milliseconds / 1000;
|
||||||
|
t.tv_nsec = (milliseconds % 1000) * 1000000;
|
||||||
|
|
||||||
|
do {
|
||||||
|
rc = nanosleep(&t, NULL);
|
||||||
|
} while (rc != 0 && errno != EINTR);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t ids[1000];
|
||||||
|
|
||||||
|
void callback(void *arg, uint64_t timeout, void *data)
|
||||||
|
{
|
||||||
|
static int idx = 0;
|
||||||
|
|
||||||
|
uint64_t id = (uintptr_t) data;
|
||||||
|
assert(ids[id] != SC_TIMER_INVALID);
|
||||||
|
ids[id] = SC_TIMER_INVALID;
|
||||||
|
assert((int) (uintptr_t) arg == 333);
|
||||||
|
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void test1(void)
|
||||||
|
{
|
||||||
|
struct sc_timer timer;
|
||||||
|
|
||||||
|
assert(sc_timer_init(&timer, time_ms()));
|
||||||
|
for (int i = 0; i < 1000; i++) {
|
||||||
|
ids[i] = sc_timer_add(&timer, (void *) (uintptr_t) i, rand() % 100);
|
||||||
|
assert(ids[i] != SC_TIMER_INVALID);
|
||||||
|
}
|
||||||
|
|
||||||
|
int t = 10000;
|
||||||
|
uint32_t n;
|
||||||
|
while (t > 0) {
|
||||||
|
n = sc_timer_timeout(&timer, time_ms(), (void *) (uintptr_t) 333,
|
||||||
|
callback);
|
||||||
|
if (timer.count == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
t -= n;
|
||||||
|
sleep_ms(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < 1000; i++) {
|
||||||
|
assert(ids[i] == SC_TIMER_INVALID);
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_timer_term(&timer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test2(void)
|
||||||
|
{
|
||||||
|
struct sc_timer timer;
|
||||||
|
|
||||||
|
assert(sc_timer_init(&timer, time_ms()));
|
||||||
|
for (int i = 0; i < 1000; i++) {
|
||||||
|
ids[i] = SC_TIMER_INVALID;
|
||||||
|
sc_timer_add(&timer, (void *) (uintptr_t) i, rand() % 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_timer_clear(&timer);
|
||||||
|
|
||||||
|
int t = 10000;
|
||||||
|
uint32_t n;
|
||||||
|
while (t > 0) {
|
||||||
|
n = sc_timer_timeout(&timer, time_ms(), (void *) (uintptr_t) 333,
|
||||||
|
callback);
|
||||||
|
if (timer.count == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
t -= n;
|
||||||
|
sleep_ms(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < 1000; i++) {
|
||||||
|
assert(ids[i] == SC_TIMER_INVALID);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < 1000; i++) {
|
||||||
|
ids[i] = sc_timer_add(&timer, (void *) (uintptr_t) i, rand() % 100);
|
||||||
|
assert(ids[i] != SC_TIMER_INVALID);
|
||||||
|
}
|
||||||
|
|
||||||
|
t = 10000;
|
||||||
|
while (t > 0) {
|
||||||
|
n = sc_timer_timeout(&timer, time_ms(), (void *) (uintptr_t) 333,
|
||||||
|
callback);
|
||||||
|
if (timer.count == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
t -= n;
|
||||||
|
sleep_ms(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < 1000; i++) {
|
||||||
|
assert(ids[i] == SC_TIMER_INVALID);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
sc_timer_term(&timer);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void test3(void)
|
||||||
|
{
|
||||||
|
struct sc_timer timer;
|
||||||
|
|
||||||
|
assert(sc_timer_init(&timer, time_ms()));
|
||||||
|
for (int i = 0; i < 1000; i++) {
|
||||||
|
ids[i] = sc_timer_add(&timer, (void *) (uintptr_t) i, rand() % 20);
|
||||||
|
assert(ids[i] != SC_TIMER_INVALID);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < 1000; i += 2) {
|
||||||
|
sc_timer_cancel(&timer, &ids[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(timer.count == 500);
|
||||||
|
sc_timer_cancel(&timer, &ids[0]);
|
||||||
|
assert(timer.count == 500);
|
||||||
|
|
||||||
|
int t = 10000;
|
||||||
|
uint32_t n;
|
||||||
|
while (t > 0) {
|
||||||
|
n = sc_timer_timeout(&timer, time_ms(), (void *) (uintptr_t) 333,
|
||||||
|
callback);
|
||||||
|
if (timer.count == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
t -= n;
|
||||||
|
sleep_ms(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < 500; i++) {
|
||||||
|
assert(ids[i] == SC_TIMER_INVALID);
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_timer_term(&timer);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef SC_HAVE_WRAP
|
||||||
|
|
||||||
|
bool fail_malloc = false;
|
||||||
|
void *__real_malloc(size_t n);
|
||||||
|
void *__wrap_malloc(size_t n)
|
||||||
|
{
|
||||||
|
if (fail_malloc) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return __real_malloc(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
void fail_test(void)
|
||||||
|
{
|
||||||
|
size_t max = SC_SIZE_MAX / sizeof(struct sc_timer_data);
|
||||||
|
struct sc_timer timer;
|
||||||
|
|
||||||
|
fail_malloc = true;
|
||||||
|
assert(sc_timer_init(&timer, time_ms()) == false);
|
||||||
|
fail_malloc = false;
|
||||||
|
assert(sc_timer_init(&timer, time_ms()) == true);
|
||||||
|
|
||||||
|
uint64_t id;
|
||||||
|
for (size_t i = 0; i < max + 100; i++) {
|
||||||
|
id = sc_timer_add(&timer, 0, i);
|
||||||
|
if (id == SC_TIMER_INVALID) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(id == SC_TIMER_INVALID);
|
||||||
|
|
||||||
|
sc_timer_term(&timer);
|
||||||
|
|
||||||
|
sc_timer_init(&timer, time_ms());
|
||||||
|
fail_malloc = true;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < 65; i++) {
|
||||||
|
id = sc_timer_add(&timer, 0, i);
|
||||||
|
if (id == SC_TIMER_INVALID) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(id == SC_TIMER_INVALID);
|
||||||
|
fail_malloc = false;
|
||||||
|
|
||||||
|
sc_timer_term(&timer);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
void fail_test(void)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
fail_test();
|
||||||
|
test1();
|
||||||
|
test2();
|
||||||
|
test3();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user