This commit is contained in:
tezc 2020-11-11 01:19:49 +03:00
commit 5b73822c55
85 changed files with 9998 additions and 0 deletions

12
.cirrus.yml Normal file
View 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
View 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
View File

@ -0,0 +1 @@
*.h linguist-language=C

147
.github/workflows/.actions.yml vendored Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

View 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
View 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;
}
```

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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;
}