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